From 556eccc9000db269afefad3b93095a05d76d8022 Mon Sep 17 00:00:00 2001 From: the-first-elder Date: Mon, 27 May 2024 16:47:45 +0100 Subject: [PATCH 01/36] added erc20 unit test --- listings/applications/erc20/src/lib.cairo | 2 +- listings/applications/erc20/src/tests.cairo | 335 +++++++++++++++++++- listings/applications/erc20/src/token.cairo | 28 +- 3 files changed, 349 insertions(+), 16 deletions(-) diff --git a/listings/applications/erc20/src/lib.cairo b/listings/applications/erc20/src/lib.cairo index 4ab2be17..51c78654 100644 --- a/listings/applications/erc20/src/lib.cairo +++ b/listings/applications/erc20/src/lib.cairo @@ -1,4 +1,4 @@ -mod token; +pub mod token; #[cfg(test)] mod tests; diff --git a/listings/applications/erc20/src/tests.cairo b/listings/applications/erc20/src/tests.cairo index 361dba07..05d83e69 100644 --- a/listings/applications/erc20/src/tests.cairo +++ b/listings/applications/erc20/src/tests.cairo @@ -1,2 +1,335 @@ -mod tests { // TODO +mod tests { + use super::super::token::{ + erc20, IERC20Dispatcher, IERC20DispatcherTrait, erc20::{Event, Transfer, Approval} + }; + + use starknet::{ + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, + contract_address_const + }; + + use starknet::testing::{set_contract_address, set_account_contract_address}; + + + fn deploy() -> (IERC20Dispatcher, ContractAddress) { + let recipient: ContractAddress = contract_address_const::<'recipient'>(); + + let token_name: felt252 = 'myToken'; + let decimals: felt252 = 18; + let initial_supply: felt252 = 100000; + let symbols: felt252 = 'mtk'; + + let (contract_address, _) = deploy_syscall( + erc20::TEST_CLASS_HASH.try_into().unwrap(), + recipient.into(), + array![recipient.into(), token_name, decimals, initial_supply, symbols].span(), + false + ) + .unwrap_syscall(); + + (IERC20Dispatcher { contract_address }, contract_address) + } + + fn recipient_address() -> ContractAddress { + let recipient: ContractAddress = contract_address_const::<'recipient'>(); + + recipient + } + + fn spender_address() -> ContractAddress { + let spender = contract_address_const::<'spender'>(); + spender + } + + fn zero_address() -> ContractAddress { + get_caller_address() + } + + #[test] + #[should_panic] + fn test_deploy_when_recipient_is_address_zero() { + let recipient: ContractAddress = zero_address(); + + let token_name: felt252 = 'myToken'; + let decimals: felt252 = 18; + let initial_supply: felt252 = 100000; + let symbols: felt252 = 'mtk'; + + let (contract_address, _) = deploy_syscall( + erc20::TEST_CLASS_HASH.try_into().unwrap(), + recipient.into(), + array![recipient.into(), token_name, decimals, initial_supply, symbols].span(), + false + ) + .unwrap_syscall(); + } + #[test] + fn test_deploy_success() { + let recipient = recipient_address(); + let (_, contract_address) = deploy(); + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) + ) + ); + } + + + #[test] + fn test_get_name() { + let (dispatcher, _) = deploy(); + let name = dispatcher.get_name(); + assert(name == 'myToken', 'wrong token name'); + } + + #[test] + fn test_get_symbol() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_symbol() == 'mtk', 'wrong symbol'); + } + + #[test] + fn test_get_decimals() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_decimals() == 18, 'wrong decimals'); + } + + #[test] + fn test_total_supply() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_total_supply() == 100000, 'wrong total supply'); + } + + #[test] + fn test_balance_of_recipient_deployed() { + let recipient = recipient_address(); + let (dispatcher, _) = deploy(); + assert(dispatcher.balance_of(recipient) == 100000, 'incorrect balance of recipient'); + } + + #[test] + fn test_allowance_without_approval() { + let caller = contract_address_const::<'caller'>(); + let spender = spender_address(); + let (dispatcher, _) = deploy(); + set_contract_address(caller); + assert(dispatcher.allowance(caller, spender) == 0, 'incorrect allowance') + } + + #[test] + fn test_allowance_after_approval() { + let caller = contract_address_const::<'caller'>(); + let spender = spender_address(); + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, 100); + assert(dispatcher.allowance(caller, spender) == 100, 'incorrect allowance') + } + + #[test] + #[should_panic] + fn test_approval_spender_is_address_zero() { + let spender: ContractAddress = zero_address(); + + let (dispatcher, _) = deploy(); + dispatcher.approve(spender, 100); + } + + #[test] + fn test_approval_success() { + let recipient = recipient_address(); + let spender = spender_address(); + let value = 100; + + let (dispatcher, contract_address) = deploy(); + let caller = contract_address_const::<'caller'>(); + set_contract_address(caller); + dispatcher.approve(spender, value); + set_contract_address(contract_address); + + // Notice the order: the first event emitted is the first to be popped. + /// ANCHOR: test_events + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value })) + ); + } + + #[test] + #[should_panic] + fn test_should_increase_allowance_with_spender_zero_address() { + let spender = zero_address(); + let (dispatcher, _) = deploy(); + dispatcher.increase_allowance(spender, 100); + } + + #[test] + fn test_should_increase_allowance() { + let caller = contract_address_const::<'caller'>(); + let recipient = recipient_address(); + let spender = spender_address(); + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, 100); + assert(dispatcher.allowance(caller, spender) == 100, 'incorrect allowance'); + set_contract_address(caller); + dispatcher.increase_allowance(spender, 100); + assert(dispatcher.allowance(caller, spender) == 200, 'incorrect increased allowance'); + + // emits one transfer event and two approval events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: 100 })) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: 200 })) + ); + } + + #[test] + #[should_panic] + fn test_should_decrease_allowance_with_spender_zero_address() { + let spender = zero_address(); + let (dispatcher, _) = deploy(); + dispatcher.decrease_allowance(spender, 100); + } + + #[test] + fn test_should_decrease_allowance() { + let caller = contract_address_const::<'caller'>(); + let recipient = recipient_address(); + let spender = spender_address(); + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, 100); + assert(dispatcher.allowance(caller, spender) == 100, 'incorrect allowance'); + + set_contract_address(caller); + dispatcher.decrease_allowance(spender, 90); + assert(dispatcher.allowance(caller, spender) == 10, 'incorrect decreased allowance'); + + // emits one transfer event and two approval events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: 100 })) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: 10 })) + ); + } + + #[test] + #[should_panic] + fn test_transfer_when_sender_is_address_zero() { + let reciever = spender_address(); + let (dispatcher, _) = deploy(); + dispatcher.transfer(reciever, 100); + } + + #[test] + #[should_panic] + fn test_transfer_when_recipient_is_address_zero() { + let caller = contract_address_const::<'caller'>(); + let reciever = zero_address(); + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.transfer(reciever, 100); + } + + #[test] + fn test_transfer_success() { + let caller = recipient_address(); + let reciever = contract_address_const::<'receiver'>(); + let recipient = recipient_address(); + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.transfer(reciever, 100); + assert_eq!(dispatcher.balance_of(reciever), 100); + + // emits two transfer events + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: 100 })) + ); + } + + + #[test] + #[should_panic] + fn test_transferFrom_when_sender_is_address_zero() { + let sender = zero_address(); + let reciever = spender_address(); + let (dispatcher, _) = deploy(); + dispatcher.transfer_from(sender, reciever, 100); + } + + #[test] + #[should_panic] + fn test_transferFrom_when_recipient_is_address_zero() { + let caller = contract_address_const::<'caller'>(); + let reciever = zero_address(); + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.transfer_from(caller, reciever, 100); + } + + #[test] + fn test_transferFrom_success() { + let caller = recipient_address(); + let reciever = contract_address_const::<'receiver'>(); + let recipient = recipient_address(); + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.transfer_from(caller, reciever, 100); + assert_eq!(dispatcher.balance_of(reciever), 100); + + // emits two transfer events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: 100 })) + ); + } } diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index da9f19a8..eb794ed8 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -2,7 +2,7 @@ use starknet::ContractAddress; // ANCHOR: interface #[starknet::interface] -trait IERC20 { +pub trait IERC20 { fn get_name(self: @TContractState) -> felt252; fn get_symbol(self: @TContractState) -> felt252; fn get_decimals(self: @TContractState) -> u8; @@ -28,7 +28,7 @@ trait IERC20 { // ANCHOR: erc20 #[starknet::contract] -mod erc20 { +pub mod erc20 { use core::num::traits::Zero; use starknet::get_caller_address; use starknet::contract_address_const; @@ -45,22 +45,22 @@ mod erc20 { } #[event] - #[derive(Drop, starknet::Event)] - enum Event { + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { Transfer: Transfer, Approval: Approval, } - #[derive(Drop, starknet::Event)] - struct Transfer { - from: ContractAddress, - to: ContractAddress, - value: felt252, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Transfer { + pub from: ContractAddress, + pub to: ContractAddress, + pub value: felt252, } - #[derive(Drop, starknet::Event)] - struct Approval { - owner: ContractAddress, - spender: ContractAddress, - value: felt252, + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Approval { + pub owner: ContractAddress, + pub spender: ContractAddress, + pub value: felt252, } mod Errors { From b3a1449522b1b80d9f0d1e4a89fa3626e4842968 Mon Sep 17 00:00:00 2001 From: the-first-elder Date: Mon, 27 May 2024 23:35:05 +0100 Subject: [PATCH 02/36] added test against custom errors --- listings/applications/erc20/src/tests.cairo | 40 ++++++++++++++++----- 1 file changed, 32 insertions(+), 8 deletions(-) diff --git a/listings/applications/erc20/src/tests.cairo b/listings/applications/erc20/src/tests.cairo index 05d83e69..679601a3 100644 --- a/listings/applications/erc20/src/tests.cairo +++ b/listings/applications/erc20/src/tests.cairo @@ -46,7 +46,8 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic(expected : ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] fn test_deploy_when_recipient_is_address_zero() { let recipient: ContractAddress = zero_address(); @@ -64,6 +65,7 @@ mod tests { .unwrap_syscall(); } #[test] + #[available_gas(2000000)] fn test_deploy_success() { let recipient = recipient_address(); let (_, contract_address) = deploy(); @@ -77,6 +79,7 @@ mod tests { #[test] + #[available_gas(2000000)] fn test_get_name() { let (dispatcher, _) = deploy(); let name = dispatcher.get_name(); @@ -84,24 +87,28 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_get_symbol() { let (dispatcher, _) = deploy(); assert(dispatcher.get_symbol() == 'mtk', 'wrong symbol'); } #[test] + #[available_gas(2000000)] fn test_get_decimals() { let (dispatcher, _) = deploy(); assert(dispatcher.get_decimals() == 18, 'wrong decimals'); } #[test] + #[available_gas(2000000)] fn test_total_supply() { let (dispatcher, _) = deploy(); assert(dispatcher.get_total_supply() == 100000, 'wrong total supply'); } #[test] + #[available_gas(2000000)] fn test_balance_of_recipient_deployed() { let recipient = recipient_address(); let (dispatcher, _) = deploy(); @@ -109,6 +116,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_allowance_without_approval() { let caller = contract_address_const::<'caller'>(); let spender = spender_address(); @@ -118,6 +126,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_allowance_after_approval() { let caller = contract_address_const::<'caller'>(); let spender = spender_address(); @@ -128,7 +137,8 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic(expected:('ERC20: approve to 0','ENTRYPOINT_FAILED'))] fn test_approval_spender_is_address_zero() { let spender: ContractAddress = zero_address(); @@ -137,6 +147,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_approval_success() { let recipient = recipient_address(); let spender = spender_address(); @@ -164,7 +175,8 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic(expected:('ERC20: approve to 0','ENTRYPOINT_FAILED'))] fn test_should_increase_allowance_with_spender_zero_address() { let spender = zero_address(); let (dispatcher, _) = deploy(); @@ -172,6 +184,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_should_increase_allowance() { let caller = contract_address_const::<'caller'>(); let recipient = recipient_address(); @@ -205,7 +218,8 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic (expected:('ERC20: approve to 0','ENTRYPOINT_FAILED'))] fn test_should_decrease_allowance_with_spender_zero_address() { let spender = zero_address(); let (dispatcher, _) = deploy(); @@ -213,6 +227,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_should_decrease_allowance() { let caller = contract_address_const::<'caller'>(); let recipient = recipient_address(); @@ -247,7 +262,8 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic (expected:('ERC20: transfer from 0','ENTRYPOINT_FAILED'))] fn test_transfer_when_sender_is_address_zero() { let reciever = spender_address(); let (dispatcher, _) = deploy(); @@ -255,7 +271,9 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic (expected:('ERC20: transfer to 0','ENTRYPOINT_FAILED'))] + #[should_panic ] fn test_transfer_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); let reciever = zero_address(); @@ -265,6 +283,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_transfer_success() { let caller = recipient_address(); let reciever = contract_address_const::<'receiver'>(); @@ -290,7 +309,9 @@ mod tests { #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic (expected:('ERC20: transfer from 0','ENTRYPOINT_FAILED'))] + #[should_panic ] fn test_transferFrom_when_sender_is_address_zero() { let sender = zero_address(); let reciever = spender_address(); @@ -299,7 +320,9 @@ mod tests { } #[test] - #[should_panic] + #[available_gas(2000000)] + #[should_panic (expected:('ERC20: transfer to 0','ENTRYPOINT_FAILED'))] + #[should_panic ] fn test_transferFrom_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); let reciever = zero_address(); @@ -309,6 +332,7 @@ mod tests { } #[test] + #[available_gas(2000000)] fn test_transferFrom_success() { let caller = recipient_address(); let reciever = contract_address_const::<'receiver'>(); From 8ee155e2612e4fd20110f3d866896b54535381f0 Mon Sep 17 00:00:00 2001 From: the-first-elder Date: Mon, 27 May 2024 23:35:45 +0100 Subject: [PATCH 03/36] formated --- listings/applications/erc20/src/tests.cairo | 22 ++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/listings/applications/erc20/src/tests.cairo b/listings/applications/erc20/src/tests.cairo index 679601a3..feb26fce 100644 --- a/listings/applications/erc20/src/tests.cairo +++ b/listings/applications/erc20/src/tests.cairo @@ -47,7 +47,7 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic(expected : ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] + #[should_panic(expected: ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] fn test_deploy_when_recipient_is_address_zero() { let recipient: ContractAddress = zero_address(); @@ -138,7 +138,7 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic(expected:('ERC20: approve to 0','ENTRYPOINT_FAILED'))] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] fn test_approval_spender_is_address_zero() { let spender: ContractAddress = zero_address(); @@ -176,7 +176,7 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic(expected:('ERC20: approve to 0','ENTRYPOINT_FAILED'))] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] fn test_should_increase_allowance_with_spender_zero_address() { let spender = zero_address(); let (dispatcher, _) = deploy(); @@ -219,7 +219,7 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic (expected:('ERC20: approve to 0','ENTRYPOINT_FAILED'))] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] fn test_should_decrease_allowance_with_spender_zero_address() { let spender = zero_address(); let (dispatcher, _) = deploy(); @@ -263,7 +263,7 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic (expected:('ERC20: transfer from 0','ENTRYPOINT_FAILED'))] + #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] fn test_transfer_when_sender_is_address_zero() { let reciever = spender_address(); let (dispatcher, _) = deploy(); @@ -272,8 +272,8 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic (expected:('ERC20: transfer to 0','ENTRYPOINT_FAILED'))] - #[should_panic ] + #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] fn test_transfer_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); let reciever = zero_address(); @@ -310,8 +310,8 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic (expected:('ERC20: transfer from 0','ENTRYPOINT_FAILED'))] - #[should_panic ] + #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] fn test_transferFrom_when_sender_is_address_zero() { let sender = zero_address(); let reciever = spender_address(); @@ -321,8 +321,8 @@ mod tests { #[test] #[available_gas(2000000)] - #[should_panic (expected:('ERC20: transfer to 0','ENTRYPOINT_FAILED'))] - #[should_panic ] + #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] fn test_transferFrom_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); let reciever = zero_address(); From 7462520104ebbfac84ffea5421776f5a96fb1419 Mon Sep 17 00:00:00 2001 From: the-first-elder Date: Tue, 28 May 2024 11:24:27 +0100 Subject: [PATCH 04/36] made requested changes and moved test to contract module --- listings/applications/erc20/src/lib.cairo | 2 +- listings/applications/erc20/src/tests.cairo | 360 +------------------- listings/applications/erc20/src/token.cairo | 340 ++++++++++++++++++ 3 files changed, 342 insertions(+), 360 deletions(-) diff --git a/listings/applications/erc20/src/lib.cairo b/listings/applications/erc20/src/lib.cairo index 51c78654..4ab2be17 100644 --- a/listings/applications/erc20/src/lib.cairo +++ b/listings/applications/erc20/src/lib.cairo @@ -1,4 +1,4 @@ -pub mod token; +mod token; #[cfg(test)] mod tests; diff --git a/listings/applications/erc20/src/tests.cairo b/listings/applications/erc20/src/tests.cairo index feb26fce..26c0e3f7 100644 --- a/listings/applications/erc20/src/tests.cairo +++ b/listings/applications/erc20/src/tests.cairo @@ -1,359 +1 @@ -mod tests { - use super::super::token::{ - erc20, IERC20Dispatcher, IERC20DispatcherTrait, erc20::{Event, Transfer, Approval} - }; - - use starknet::{ - ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, - contract_address_const - }; - - use starknet::testing::{set_contract_address, set_account_contract_address}; - - - fn deploy() -> (IERC20Dispatcher, ContractAddress) { - let recipient: ContractAddress = contract_address_const::<'recipient'>(); - - let token_name: felt252 = 'myToken'; - let decimals: felt252 = 18; - let initial_supply: felt252 = 100000; - let symbols: felt252 = 'mtk'; - - let (contract_address, _) = deploy_syscall( - erc20::TEST_CLASS_HASH.try_into().unwrap(), - recipient.into(), - array![recipient.into(), token_name, decimals, initial_supply, symbols].span(), - false - ) - .unwrap_syscall(); - - (IERC20Dispatcher { contract_address }, contract_address) - } - - fn recipient_address() -> ContractAddress { - let recipient: ContractAddress = contract_address_const::<'recipient'>(); - - recipient - } - - fn spender_address() -> ContractAddress { - let spender = contract_address_const::<'spender'>(); - spender - } - - fn zero_address() -> ContractAddress { - get_caller_address() - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] - fn test_deploy_when_recipient_is_address_zero() { - let recipient: ContractAddress = zero_address(); - - let token_name: felt252 = 'myToken'; - let decimals: felt252 = 18; - let initial_supply: felt252 = 100000; - let symbols: felt252 = 'mtk'; - - let (contract_address, _) = deploy_syscall( - erc20::TEST_CLASS_HASH.try_into().unwrap(), - recipient.into(), - array![recipient.into(), token_name, decimals, initial_supply, symbols].span(), - false - ) - .unwrap_syscall(); - } - #[test] - #[available_gas(2000000)] - fn test_deploy_success() { - let recipient = recipient_address(); - let (_, contract_address) = deploy(); - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some( - Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) - ) - ); - } - - - #[test] - #[available_gas(2000000)] - fn test_get_name() { - let (dispatcher, _) = deploy(); - let name = dispatcher.get_name(); - assert(name == 'myToken', 'wrong token name'); - } - - #[test] - #[available_gas(2000000)] - fn test_get_symbol() { - let (dispatcher, _) = deploy(); - assert(dispatcher.get_symbol() == 'mtk', 'wrong symbol'); - } - - #[test] - #[available_gas(2000000)] - fn test_get_decimals() { - let (dispatcher, _) = deploy(); - assert(dispatcher.get_decimals() == 18, 'wrong decimals'); - } - - #[test] - #[available_gas(2000000)] - fn test_total_supply() { - let (dispatcher, _) = deploy(); - assert(dispatcher.get_total_supply() == 100000, 'wrong total supply'); - } - - #[test] - #[available_gas(2000000)] - fn test_balance_of_recipient_deployed() { - let recipient = recipient_address(); - let (dispatcher, _) = deploy(); - assert(dispatcher.balance_of(recipient) == 100000, 'incorrect balance of recipient'); - } - - #[test] - #[available_gas(2000000)] - fn test_allowance_without_approval() { - let caller = contract_address_const::<'caller'>(); - let spender = spender_address(); - let (dispatcher, _) = deploy(); - set_contract_address(caller); - assert(dispatcher.allowance(caller, spender) == 0, 'incorrect allowance') - } - - #[test] - #[available_gas(2000000)] - fn test_allowance_after_approval() { - let caller = contract_address_const::<'caller'>(); - let spender = spender_address(); - let (dispatcher, _) = deploy(); - set_contract_address(caller); - dispatcher.approve(spender, 100); - assert(dispatcher.allowance(caller, spender) == 100, 'incorrect allowance') - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] - fn test_approval_spender_is_address_zero() { - let spender: ContractAddress = zero_address(); - - let (dispatcher, _) = deploy(); - dispatcher.approve(spender, 100); - } - - #[test] - #[available_gas(2000000)] - fn test_approval_success() { - let recipient = recipient_address(); - let spender = spender_address(); - let value = 100; - - let (dispatcher, contract_address) = deploy(); - let caller = contract_address_const::<'caller'>(); - set_contract_address(caller); - dispatcher.approve(spender, value); - set_contract_address(contract_address); - - // Notice the order: the first event emitted is the first to be popped. - /// ANCHOR: test_events - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some( - Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) - ) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Approval(Approval { owner: caller, spender, value })) - ); - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] - fn test_should_increase_allowance_with_spender_zero_address() { - let spender = zero_address(); - let (dispatcher, _) = deploy(); - dispatcher.increase_allowance(spender, 100); - } - - #[test] - #[available_gas(2000000)] - fn test_should_increase_allowance() { - let caller = contract_address_const::<'caller'>(); - let recipient = recipient_address(); - let spender = spender_address(); - let (dispatcher, contract_address) = deploy(); - set_contract_address(caller); - dispatcher.approve(spender, 100); - assert(dispatcher.allowance(caller, spender) == 100, 'incorrect allowance'); - set_contract_address(caller); - dispatcher.increase_allowance(spender, 100); - assert(dispatcher.allowance(caller, spender) == 200, 'incorrect increased allowance'); - - // emits one transfer event and two approval events - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some( - Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) - ) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Approval(Approval { owner: caller, spender, value: 100 })) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Approval(Approval { owner: caller, spender, value: 200 })) - ); - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] - fn test_should_decrease_allowance_with_spender_zero_address() { - let spender = zero_address(); - let (dispatcher, _) = deploy(); - dispatcher.decrease_allowance(spender, 100); - } - - #[test] - #[available_gas(2000000)] - fn test_should_decrease_allowance() { - let caller = contract_address_const::<'caller'>(); - let recipient = recipient_address(); - let spender = spender_address(); - let (dispatcher, contract_address) = deploy(); - set_contract_address(caller); - dispatcher.approve(spender, 100); - assert(dispatcher.allowance(caller, spender) == 100, 'incorrect allowance'); - - set_contract_address(caller); - dispatcher.decrease_allowance(spender, 90); - assert(dispatcher.allowance(caller, spender) == 10, 'incorrect decreased allowance'); - - // emits one transfer event and two approval events - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some( - Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) - ) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Approval(Approval { owner: caller, spender, value: 100 })) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Approval(Approval { owner: caller, spender, value: 10 })) - ); - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] - fn test_transfer_when_sender_is_address_zero() { - let reciever = spender_address(); - let (dispatcher, _) = deploy(); - dispatcher.transfer(reciever, 100); - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] - #[should_panic] - fn test_transfer_when_recipient_is_address_zero() { - let caller = contract_address_const::<'caller'>(); - let reciever = zero_address(); - let (dispatcher, _) = deploy(); - set_contract_address(caller); - dispatcher.transfer(reciever, 100); - } - - #[test] - #[available_gas(2000000)] - fn test_transfer_success() { - let caller = recipient_address(); - let reciever = contract_address_const::<'receiver'>(); - let recipient = recipient_address(); - let (dispatcher, contract_address) = deploy(); - set_contract_address(caller); - dispatcher.transfer(reciever, 100); - assert_eq!(dispatcher.balance_of(reciever), 100); - - // emits two transfer events - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some( - Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) - ) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: 100 })) - ); - } - - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] - #[should_panic] - fn test_transferFrom_when_sender_is_address_zero() { - let sender = zero_address(); - let reciever = spender_address(); - let (dispatcher, _) = deploy(); - dispatcher.transfer_from(sender, reciever, 100); - } - - #[test] - #[available_gas(2000000)] - #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] - #[should_panic] - fn test_transferFrom_when_recipient_is_address_zero() { - let caller = contract_address_const::<'caller'>(); - let reciever = zero_address(); - let (dispatcher, _) = deploy(); - set_contract_address(caller); - dispatcher.transfer_from(caller, reciever, 100); - } - - #[test] - #[available_gas(2000000)] - fn test_transferFrom_success() { - let caller = recipient_address(); - let reciever = contract_address_const::<'receiver'>(); - let recipient = recipient_address(); - let (dispatcher, contract_address) = deploy(); - set_contract_address(caller); - dispatcher.transfer_from(caller, reciever, 100); - assert_eq!(dispatcher.balance_of(reciever), 100); - - // emits two transfer events - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some( - Event::Transfer(Transfer { from: zero_address(), to: recipient, value: 100000 }) - ) - ); - - assert_eq!( - starknet::testing::pop_log(contract_address), - Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: 100 })) - ); - } -} +mod tests {} diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index eb794ed8..9e05d4c9 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -212,4 +212,344 @@ pub mod erc20 { } // ANCHOR_END: erc20 +#[cfg(test)] +mod tests { + use super::{erc20, IERC20Dispatcher, IERC20DispatcherTrait, erc20::{Event, Transfer, Approval}}; + use starknet::{ + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, + contract_address_const + }; + + use starknet::testing::{set_contract_address, set_account_contract_address}; + + const token_name: felt252 = 'myToken'; + const decimals: u8 = 18; + const initial_supply: felt252 = 100000; + const symbols: felt252 = 'mtk'; + + fn deploy() -> (IERC20Dispatcher, ContractAddress) { + let recipient: ContractAddress = contract_address_const::<'initialzed_recipient'>(); + + let (contract_address, _) = deploy_syscall( + erc20::TEST_CLASS_HASH.try_into().unwrap(), + recipient.into(), + array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), + false + ) + .unwrap_syscall(); + + (IERC20Dispatcher { contract_address }, contract_address) + } + + + #[test] + #[should_panic(expected: ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] + fn test_deploy_when_recipient_is_address_zero() { + let recipient: ContractAddress = get_caller_address(); + + let (contract_address, _) = deploy_syscall( + erc20::TEST_CLASS_HASH.try_into().unwrap(), + recipient.into(), + array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), + false + ) + .unwrap_syscall(); + } + #[test] + fn test_deploy_success() { + let recipient = contract_address_const::<'initialzed_recipient'>(); + let (_, contract_address) = deploy(); + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + ) + ) + ); + } + + + #[test] + fn test_get_name() { + let (dispatcher, _) = deploy(); + let name = dispatcher.get_name(); + assert(name == token_name, 'wrong token name'); + } + + #[test] + fn test_get_symbol() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_symbol() == symbols, 'wrong symbol'); + } + + #[test] + fn test_get_decimals() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_decimals() == decimals, 'wrong decimals'); + } + + #[test] + fn test_total_supply() { + let (dispatcher, _) = deploy(); + assert(dispatcher.get_total_supply() == initial_supply, 'wrong total supply'); + } + + #[test] + fn test_balance_of_recipient_deployed() { + let recipient = contract_address_const::<'initialzed_recipient'>(); + let (dispatcher, _) = deploy(); + assert( + dispatcher.balance_of(recipient) == initial_supply, 'incorrect balance of recipient' + ); + } + + #[test] + fn test_allowance_without_approval() { + let caller = contract_address_const::<'caller'>(); + let spender = contract_address_const::<'spender'>(); + let (dispatcher, _) = deploy(); + set_contract_address(caller); + assert(dispatcher.allowance(caller, spender) == 0, 'incorrect allowance') + } + + #[test] + fn test_allowance_after_approval() { + let caller = contract_address_const::<'caller'>(); + let spender = contract_address_const::<'spender'>(); + let (dispatcher, _) = deploy(); + let amount = 100; + set_contract_address(caller); + dispatcher.approve(spender, amount); + assert(dispatcher.allowance(caller, spender) == amount, 'incorrect allowance') + } + + #[test] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] + fn test_approval_spender_is_address_zero() { + let spender: ContractAddress = get_caller_address(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.approve(spender, amount); + } + + #[test] + fn test_approval_success() { + let recipient = contract_address_const::<'initialzed_recipient'>(); + let spender = contract_address_const::<'spender'>(); + let value = 100; + let (dispatcher, contract_address) = deploy(); + let caller = contract_address_const::<'caller'>(); + set_contract_address(caller); + dispatcher.approve(spender, value); + set_contract_address(contract_address); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value })) + ); + } + + #[test] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] + fn test_should_increase_allowance_with_spender_zero_address() { + let spender = get_caller_address(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.increase_allowance(spender, amount); + } + + #[test] + fn test_should_increase_allowance() { + let caller = contract_address_const::<'caller'>(); + let recipient = contract_address_const::<'initialzed_recipient'>(); + let spender = contract_address_const::<'spender'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, amount); + assert(dispatcher.allowance(caller, spender) == amount, 'incorrect allowance'); + set_contract_address(caller); + dispatcher.increase_allowance(spender, 100); + assert( + dispatcher.allowance(caller, spender) == amount + 100, 'incorrect increased allowance' + ); + + // emits one transfer event and two approval events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount })) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount + 100 })) + ); + } + + #[test] + #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] + fn test_should_decrease_allowance_with_spender_zero_address() { + let spender = get_caller_address(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.decrease_allowance(spender, amount); + } + + #[test] + fn test_should_decrease_allowance() { + let caller = contract_address_const::<'caller'>(); + let recipient = contract_address_const::<'initialzed_recipient'>(); + let spender = contract_address_const::<'spender'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.approve(spender, amount); + assert(dispatcher.allowance(caller, spender) == amount, 'incorrect allowance'); + + set_contract_address(caller); + dispatcher.decrease_allowance(spender, 90); + assert( + dispatcher.allowance(caller, spender) == amount - 90, 'incorrect decreased allowance' + ); + + // emits one transfer event and two approval events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount })) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Approval(Approval { owner: caller, spender, value: amount - 90 })) + ); + } + + #[test] + #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] + fn test_transfer_when_sender_is_address_zero() { + let reciever = contract_address_const::<'spender'>(); + let amount = 100; + let (dispatcher, _) = deploy(); + dispatcher.transfer(reciever, amount); + } + + #[test] + #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] + fn test_transfer_when_recipient_is_address_zero() { + let caller = contract_address_const::<'caller'>(); + let reciever = get_caller_address(); + let amount = 100; + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.transfer(reciever, amount); + } + + #[test] + fn test_transfer_success() { + let caller = contract_address_const::<'initialzed_recipient'>(); + let reciever = contract_address_const::<'receiver'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.transfer(reciever, amount); + assert_eq!(dispatcher.balance_of(reciever), amount); + + // emits two transfer events + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: get_caller_address(), to: caller, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: amount })) + ); + } + + + #[test] + #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] + fn test_transferFrom_when_sender_is_address_zero() { + let sender = get_caller_address(); + let amount = 100; + let reciever = contract_address_const::<'spender'>(); + let (dispatcher, _) = deploy(); + dispatcher.transfer_from(sender, reciever, amount); + } + + #[test] + #[should_panic(expected: ('ERC20: transfer to 0', 'ENTRYPOINT_FAILED'))] + #[should_panic] + fn test_transferFrom_when_recipient_is_address_zero() { + let caller = contract_address_const::<'caller'>(); + let reciever = get_caller_address(); + let amount = 100; + let (dispatcher, _) = deploy(); + set_contract_address(caller); + dispatcher.transfer_from(caller, reciever, amount); + } + + #[test] + fn test_transferFrom_success() { + let caller = contract_address_const::<'initialzed_recipient'>(); + let reciever = contract_address_const::<'receiver'>(); + let amount = 100; + let (dispatcher, contract_address) = deploy(); + set_contract_address(caller); + dispatcher.transfer_from(caller, reciever, amount); + assert_eq!(dispatcher.balance_of(reciever), amount); + + // emits two transfer events + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some( + Event::Transfer( + Transfer { from: get_caller_address(), to: caller, value: initial_supply } + ) + ) + ); + + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: amount })) + ); + } +} From 4cc0c413beaa8ee976ceb322b96d354025a9be54 Mon Sep 17 00:00:00 2001 From: the-first-elder Date: Tue, 28 May 2024 17:52:26 +0100 Subject: [PATCH 05/36] fixed zero address --- listings/applications/erc20/src/token.cairo | 31 ++++++++++----------- 1 file changed, 14 insertions(+), 17 deletions(-) diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index 9e05d4c9..13066c29 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -220,6 +220,7 @@ mod tests { ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, contract_address_const }; + use core::num::traits::Zero; use starknet::testing::{set_contract_address, set_account_contract_address}; @@ -246,7 +247,7 @@ mod tests { #[test] #[should_panic(expected: ('ERC20: mint to 0', 'CONSTRUCTOR_FAILED'))] fn test_deploy_when_recipient_is_address_zero() { - let recipient: ContractAddress = get_caller_address(); + let recipient: ContractAddress = Zero::zero(); let (contract_address, _) = deploy_syscall( erc20::TEST_CLASS_HASH.try_into().unwrap(), @@ -264,7 +265,7 @@ mod tests { starknet::testing::pop_log(contract_address), Option::Some( Event::Transfer( - Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } ) ) ); @@ -328,7 +329,7 @@ mod tests { #[test] #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] fn test_approval_spender_is_address_zero() { - let spender: ContractAddress = get_caller_address(); + let spender: ContractAddress = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); dispatcher.approve(spender, amount); @@ -349,7 +350,7 @@ mod tests { starknet::testing::pop_log(contract_address), Option::Some( Event::Transfer( - Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } ) ) ); @@ -363,7 +364,7 @@ mod tests { #[test] #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] fn test_should_increase_allowance_with_spender_zero_address() { - let spender = get_caller_address(); + let spender = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); dispatcher.increase_allowance(spender, amount); @@ -391,7 +392,7 @@ mod tests { starknet::testing::pop_log(contract_address), Option::Some( Event::Transfer( - Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } ) ) ); @@ -410,7 +411,7 @@ mod tests { #[test] #[should_panic(expected: ('ERC20: approve to 0', 'ENTRYPOINT_FAILED'))] fn test_should_decrease_allowance_with_spender_zero_address() { - let spender = get_caller_address(); + let spender = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); dispatcher.decrease_allowance(spender, amount); @@ -439,7 +440,7 @@ mod tests { starknet::testing::pop_log(contract_address), Option::Some( Event::Transfer( - Transfer { from: get_caller_address(), to: recipient, value: initial_supply } + Transfer { from: Zero::zero(), to: recipient, value: initial_supply } ) ) ); @@ -469,7 +470,7 @@ mod tests { #[should_panic] fn test_transfer_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); - let reciever = get_caller_address(); + let reciever = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); set_contract_address(caller); @@ -490,9 +491,7 @@ mod tests { assert_eq!( starknet::testing::pop_log(contract_address), Option::Some( - Event::Transfer( - Transfer { from: get_caller_address(), to: caller, value: initial_supply } - ) + Event::Transfer(Transfer { from: Zero::zero(), to: caller, value: initial_supply }) ) ); @@ -507,7 +506,7 @@ mod tests { #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] #[should_panic] fn test_transferFrom_when_sender_is_address_zero() { - let sender = get_caller_address(); + let sender = Zero::zero(); let amount = 100; let reciever = contract_address_const::<'spender'>(); let (dispatcher, _) = deploy(); @@ -519,7 +518,7 @@ mod tests { #[should_panic] fn test_transferFrom_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); - let reciever = get_caller_address(); + let reciever = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); set_contract_address(caller); @@ -541,9 +540,7 @@ mod tests { assert_eq!( starknet::testing::pop_log(contract_address), Option::Some( - Event::Transfer( - Transfer { from: get_caller_address(), to: caller, value: initial_supply } - ) + Event::Transfer(Transfer { from: Zero::zero(), to: caller, value: initial_supply }) ) ); From 57aeb625b490562591fc0b85a8b1ba37ac533623 Mon Sep 17 00:00:00 2001 From: julio4 Date: Thu, 30 May 2024 12:15:39 +0900 Subject: [PATCH 06/36] chore: Remove unused test files --- listings/applications/erc20/src/lib.cairo | 3 --- listings/applications/erc20/src/tests.cairo | 1 - listings/applications/erc20/src/token.cairo | 2 +- 3 files changed, 1 insertion(+), 5 deletions(-) delete mode 100644 listings/applications/erc20/src/tests.cairo diff --git a/listings/applications/erc20/src/lib.cairo b/listings/applications/erc20/src/lib.cairo index 4ab2be17..40d3ff58 100644 --- a/listings/applications/erc20/src/lib.cairo +++ b/listings/applications/erc20/src/lib.cairo @@ -1,4 +1 @@ mod token; - -#[cfg(test)] -mod tests; diff --git a/listings/applications/erc20/src/tests.cairo b/listings/applications/erc20/src/tests.cairo deleted file mode 100644 index 26c0e3f7..00000000 --- a/listings/applications/erc20/src/tests.cairo +++ /dev/null @@ -1 +0,0 @@ -mod tests {} diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index 13066c29..51660642 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -249,7 +249,7 @@ mod tests { fn test_deploy_when_recipient_is_address_zero() { let recipient: ContractAddress = Zero::zero(); - let (contract_address, _) = deploy_syscall( + let (_contract_address, _) = deploy_syscall( erc20::TEST_CLASS_HASH.try_into().unwrap(), recipient.into(), array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), From e836f0905efafbb35d37ef451537f0fa71bffb2e Mon Sep 17 00:00:00 2001 From: Okoli Evans Date: Thu, 30 May 2024 12:05:31 +0100 Subject: [PATCH 07/36] Library calls (#194) * final push * updated the Summary.md * fix: fmt and minor edit --------- Co-authored-by: julio4 --- Scarb.lock | 4 ++ .../library_calls/.gitignore | 1 + .../library_calls/Scarb.toml | 14 +++++ .../library_calls/src/lib.cairo | 4 ++ .../library_calls/src/library_call.cairo | 51 +++++++++++++++++++ .../library_calls/src/tests.cairo | 25 +++++++++ src/SUMMARY.md | 1 + src/ch02/library_calls.md | 16 ++++++ 8 files changed, 116 insertions(+) create mode 100644 listings/advanced-concepts/library_calls/.gitignore create mode 100644 listings/advanced-concepts/library_calls/Scarb.toml create mode 100644 listings/advanced-concepts/library_calls/src/lib.cairo create mode 100644 listings/advanced-concepts/library_calls/src/library_call.cairo create mode 100644 listings/advanced-concepts/library_calls/src/tests.cairo create mode 100644 src/ch02/library_calls.md diff --git a/Scarb.lock b/Scarb.lock index 60e669e4..7dab0bb1 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -80,6 +80,10 @@ version = "0.1.0" name = "interfaces_traits" version = "0.1.0" +[[package]] +name = "library_calls" +version = "0.1.0" + [[package]] name = "mappings" version = "0.1.0" diff --git a/listings/advanced-concepts/library_calls/.gitignore b/listings/advanced-concepts/library_calls/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/advanced-concepts/library_calls/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/advanced-concepts/library_calls/Scarb.toml b/listings/advanced-concepts/library_calls/Scarb.toml new file mode 100644 index 00000000..0ea12255 --- /dev/null +++ b/listings/advanced-concepts/library_calls/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "library_calls" +version.workspace = true +edition = "2023_11" + +# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html + +[dependencies] +starknet.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] \ No newline at end of file diff --git a/listings/advanced-concepts/library_calls/src/lib.cairo b/listings/advanced-concepts/library_calls/src/lib.cairo new file mode 100644 index 00000000..60ee240e --- /dev/null +++ b/listings/advanced-concepts/library_calls/src/lib.cairo @@ -0,0 +1,4 @@ +mod library_call; + +#[cfg(test)] +mod tests; diff --git a/listings/advanced-concepts/library_calls/src/library_call.cairo b/listings/advanced-concepts/library_calls/src/library_call.cairo new file mode 100644 index 00000000..a9f7cd56 --- /dev/null +++ b/listings/advanced-concepts/library_calls/src/library_call.cairo @@ -0,0 +1,51 @@ +// ANCHOR: library_dispatcher +#[starknet::interface] +pub trait IMathUtils { + fn add(ref self: T, x: u32, y: u32) -> u32; + fn set_class_hash(ref self: T, class_hash: starknet::ClassHash); +} + +// contract A +#[starknet::contract] +pub mod MathUtils { + #[storage] + struct Storage {} + + #[abi(embed_v0)] + impl ImathUtilsImpl of super::IMathUtils { + fn add(ref self: ContractState, x: u32, y: u32) -> u32 { + x + y + } + + fn set_class_hash(ref self: ContractState, class_hash: starknet::ClassHash) {} + } +} + + +// contract B to make library call to the class of contract A +#[starknet::contract] +pub mod MathUtilsLibraryCall { + use starknet::{class_hash::class_hash_const, ContractAddress}; + use super::{IMathUtilsDispatcherTrait, IMathUtilsLibraryDispatcher}; + + #[storage] + struct Storage { + value: u32, + lib_class_hash: starknet::ClassHash, + } + + #[abi(embed_v0)] + impl MathUtils of super::IMathUtils { + fn add(ref self: ContractState, x: u32, y: u32) -> u32 { + IMathUtilsLibraryDispatcher { class_hash: self.lib_class_hash.read() }.add(x, y) + } + + #[abi(embed_v0)] + fn set_class_hash(ref self: ContractState, class_hash: starknet::ClassHash) { + self.lib_class_hash.write(class_hash); + } + } +} +// ANCHOR_END: library_dispatcher + + diff --git a/listings/advanced-concepts/library_calls/src/tests.cairo b/listings/advanced-concepts/library_calls/src/tests.cairo new file mode 100644 index 00000000..1b9ecdf0 --- /dev/null +++ b/listings/advanced-concepts/library_calls/src/tests.cairo @@ -0,0 +1,25 @@ +mod tests { + use starknet::syscalls::{deploy_syscall}; + use starknet::SyscallResultTrait; + use library_calls::library_call::{ + MathUtils, MathUtilsLibraryCall, IMathUtilsDispatcher, IMathUtilsDispatcherTrait + }; + + #[test] + #[available_gas(20000000)] + fn test_library_dispatcher() { + let math_utils_class_hash: starknet::ClassHash = MathUtils::TEST_CLASS_HASH + .try_into() + .unwrap(); + let mut calldata: Array = array![]; + let (address, _) = deploy_syscall( + MathUtilsLibraryCall::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false + ) + .unwrap_syscall(); + let mut contract = IMathUtilsDispatcher { contract_address: address }; + + contract.set_class_hash(math_utils_class_hash); + let mut result = contract.add(30, 5); + assert_eq!(result, 35, "Wrong result"); + } +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index c237c3d7..a02b5802 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -71,3 +71,4 @@ Summary - [List](./ch02/list.md) - [Plugins](./ch02/plugins.md) - [Signature Verification](./ch02/signature_verification.md) +- [Library Calls](./ch02/library_calls.md) diff --git a/src/ch02/library_calls.md b/src/ch02/library_calls.md new file mode 100644 index 00000000..85813bba --- /dev/null +++ b/src/ch02/library_calls.md @@ -0,0 +1,16 @@ +# Library Dispatcher + +External calls can be made on Starknet by two means: Contract dispatchers or Library dispatchers. Dispatchers are automatically created and exported by the compiler when a contract interface is defined. + +With Contract dispatcher we are calling an already deployed contract (with associated state), therefore contract address is passed to the dispatcher to make the call. However, with library dispatcher we are simply making function calls to declared contract **classes** (stateless). + +Contract dispatcher call is synonymous to external calls in Solidity, while library dispatcher call is synonymous to delegate call. + +For further reading: [Cairo book](https://book.cairo-lang.org/ch15-02-contract-dispatchers-library-dispatchers-and-system-calls.html?highlight=library%20dispatchers#library-dispatcher). + +```rust +{{#rustdoc_include ../../listings/advanced-concepts/library_calls/src/library_call.cairo:library_dispatcher}} +``` + + + From bc223b4954613e0d8daec838ee3131b3fca499d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Misi=C4=87?= Date: Mon, 3 Jun 2024 16:33:28 +0200 Subject: [PATCH 08/36] Chapter 0 > Basics: wording + grammar fixes (#207) * sierra->Sierra + plural/singular form fixes * ch00 > Missing attr. elems, wrong attr. names * missing dot in messages + update how legacymap modulo format * ch00 > errors > add indent to comment in complex section * ch00 > events > wording, missing code quotes * ch00 > syscalls > wording * ch00 > bytearray > wording * ch00 > stor.custom types > wording * ch00 > cust.types in entrypoints > wording * ch00 > documentation > wording * Revert comment format changes * Simplify panic_with_felt252 related comment in errors.md * Fixes for ch00 * Wording in cheatsheet * Comment update in type_casting --------- Co-authored-by: Nenad --- .../cairo_cheatsheet/src/array_example.cairo | 2 +- .../src/type_casting_example.cairo | 2 +- .../src/while_let_example.cairo | 2 +- .../custom_type_serde/src/contract.cairo | 2 +- .../getting-started/events/src/counter.cairo | 2 +- .../testing_how_to/src/contract.cairo | 4 +- .../visibility/src/visibility.cairo | 12 ++-- po/es.po | 62 +++++++++---------- po/messages.pot | 20 +++--- po/zh-cn.po | 32 +++++----- src/ch00/basics/bytearrays-strings.md | 4 +- .../basics/custom-types-in-entrypoints.md | 5 +- src/ch00/basics/documentation.md | 10 +-- src/ch00/basics/errors.md | 8 +-- src/ch00/basics/events.md | 4 +- src/ch00/basics/mappings.md | 3 +- src/ch00/basics/storage.md | 4 +- src/ch00/basics/storing-custom-types.md | 2 +- src/ch00/basics/syscalls.md | 12 ++-- src/ch00/basics/variables.md | 4 +- src/ch00/basics/visibility-mutability.md | 10 +-- src/ch00/cairo_cheatsheet/felt.md | 6 +- src/ch00/cairo_cheatsheet/loop.md | 1 + src/ch00/cairo_cheatsheet/mapping.md | 4 +- src/ch00/cairo_cheatsheet/match.md | 3 +- src/ch00/cairo_cheatsheet/struct.md | 3 +- src/ch00/cairo_cheatsheet/tuples.md | 1 + src/ch00/cairo_cheatsheet/type_casting.md | 5 +- src/ch00/cairo_cheatsheet/while.md | 3 +- src/ch00/cairo_cheatsheet/while_let.md | 3 +- .../interacting/calling_other_contracts.md | 6 +- src/ch00/interacting/factory.md | 8 +-- src/ch00/interacting/interfaces-traits.md | 6 +- src/ch00/testing/contract-testing.md | 16 ++--- src/starknet-by-example.md | 9 +-- 35 files changed, 144 insertions(+), 136 deletions(-) diff --git a/listings/getting-started/cairo_cheatsheet/src/array_example.cairo b/listings/getting-started/cairo_cheatsheet/src/array_example.cairo index c06ac7cf..8176491a 100644 --- a/listings/getting-started/cairo_cheatsheet/src/array_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/array_example.cairo @@ -12,6 +12,6 @@ fn array() -> bool { let second_value = *arr.at(0); assert(second_value == 20, 'second value should match'); - // Returns true if an array is empty, then false if it isn't. + // Returns true if an array is empty, and false if it isn't. arr.is_empty() } diff --git a/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo b/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo index 0c7362d0..17150e32 100644 --- a/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo @@ -21,7 +21,7 @@ fn type_casting() { let _new_u256: u256 = my_felt252.into(); let _new_felt252: felt252 = new_u16.into(); - //note a usize is smaller than a felt so we use the try_into + // Note: usize is smaller than felt252, so we use try_into let _new_usize: usize = my_felt252.try_into().unwrap(); // ANCHOR_END: sheet } diff --git a/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo b/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo index ebdc19ce..edc475e0 100644 --- a/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo @@ -2,7 +2,7 @@ fn while_let() { // ANCHOR: sheet let mut option = Option::Some(0_usize); - // "while `let` destructures `option` into `Some(i)`: + // "while `let` destructures `option` into `Some(i)`, // evaluate the block (`{}`), else `break` while let Option::Some(i) = option { diff --git a/listings/getting-started/custom_type_serde/src/contract.cairo b/listings/getting-started/custom_type_serde/src/contract.cairo index beea5a07..9a320b75 100644 --- a/listings/getting-started/custom_type_serde/src/contract.cairo +++ b/listings/getting-started/custom_type_serde/src/contract.cairo @@ -6,7 +6,7 @@ pub trait ISerdeCustomType { // ANCHOR: contract // Deriving the `Serde` trait allows us to use -// the Person type as an entrypoint parameter and return value +// the `Person` type as an entrypoint parameter and as a return value #[derive(Drop, Serde)] pub struct Person { pub age: u8, diff --git a/listings/getting-started/events/src/counter.cairo b/listings/getting-started/events/src/counter.cairo index 346e47b5..10d879a6 100644 --- a/listings/getting-started/events/src/counter.cairo +++ b/listings/getting-started/events/src/counter.cairo @@ -17,7 +17,7 @@ pub mod EventCounter { #[event] #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] // The event enum must be annotated with the `#[event]` attribute. - // It must also derive atleast `Drop` and `starknet::Event` traits. + // It must also derive at least the `Drop` and `starknet::Event` traits. pub enum Event { CounterIncreased: CounterIncreased, UserIncreaseCounter: UserIncreaseCounter diff --git a/listings/getting-started/testing_how_to/src/contract.cairo b/listings/getting-started/testing_how_to/src/contract.cairo index 0ef9f56e..6eab76dd 100644 --- a/listings/getting-started/testing_how_to/src/contract.cairo +++ b/listings/getting-started/testing_how_to/src/contract.cairo @@ -202,14 +202,14 @@ mod tests_with_states { assert_eq!(state.get_value(), initial_value); assert_eq!(state.get_owner(), owner); - // Mutating the state from the contract change the testing state + // Mutating the state from the contract changes the testing state set_contract_address(owner); let new_value: u32 = 20; contract.set_value(new_value); set_contract_address(contract.contract_address); assert_eq!(state.get_value(), new_value); - // Mutating the state from the testing state change the contract state + // Mutating the state from the testing state changes the contract state set_caller_address(owner); state.set_value(initial_value); assert_eq!(contract.get_value(), initial_value); diff --git a/listings/getting-started/visibility/src/visibility.cairo b/listings/getting-started/visibility/src/visibility.cairo index f3482ee2..25256830 100644 --- a/listings/getting-started/visibility/src/visibility.cairo +++ b/listings/getting-started/visibility/src/visibility.cairo @@ -12,21 +12,21 @@ pub mod ExampleContract { value: u32 } - // The `abi(embed_v0)` attribute indicates that all + // The `#[abi(embed_v0)]` attribute indicates that all // the functions in this implementation can be called externally. // Omitting this attribute would make all the functions internal. #[abi(embed_v0)] impl ExampleContract of super::IExampleContract { // The `set` function can be called externally - // because it is written inside an implementation marked as `#[external]`. + // because it is written inside an implementation marked as `#[abi(embed_v0)]`. // It can modify the contract's state as it is passed as a reference. fn set(ref self: ContractState, value: u32) { self.value.write(value); } // The `get` function can be called externally - // because it is written inside an implementation marked as `#[external]`. - // However, it can't modify the contract's state is passed as a snapshot + // because it is written inside an implementation marked as `#[abi(embed_v0)]`. + // However, it can't modify the contract's state, as it is passed as a snapshot // -> It's only a "view" function. fn get(self: @ContractState) -> u32 { // We can call an internal function from any functions within the contract @@ -34,7 +34,7 @@ pub mod ExampleContract { } } - // The lack of the `external` attribute indicates that all the functions in + // The lack of the `#[abi(embed_v0)]` attribute indicates that all the functions in // this implementation can only be called internally. // We name the trait `PrivateFunctionsTrait` to indicate that it is an // internal trait allowing us to call internal functions. @@ -43,7 +43,7 @@ pub mod ExampleContract { // The `_read_value` function is outside the implementation that is // marked as `#[abi(embed_v0)]`, so it's an _internal_ function // and can only be called from within the contract. - // However, it can't modify the contract's state is passed + // However, it can't modify the contract's state, as it is passed // as a snapshot: it is only a "view" function. fn _read_value(self: @ContractState) -> u32 { self.value.read() diff --git a/po/es.po b/po/es.po index 6693ade5..2108a25c 100644 --- a/po/es.po +++ b/po/es.po @@ -426,11 +426,11 @@ msgstr "" #: src/ch00/basics/storage.md:29 msgid "" -"> Actually these two contracts have the same underlying sierra program.\n" +"> Actually these two contracts have the same underlying Sierra program.\n" "> From the compiler's perspective, the storage variables don't exist until " "they are used." msgstr "" -"> En realidad estos dos contratos tienen el mismo programa sierra " +"> En realidad estos dos contratos tienen el mismo programa Sierra " "subyacente.\n" "> Desde la perspectiva del compilador, las variables de almacenamiento no " "existen hasta que se utilizan." @@ -1004,14 +1004,14 @@ msgid "" " }\n" "\n" "\n" -" // The `abi(embed_v0)` attribute indicates that all the functions in " +" // The `#[abi(embed_v0)]` attribute indicates that all the functions in " "this implementation can be called externally.\n" " // Omitting this attribute would make all the functions in this " "implementation internal.\n" " #[abi(embed_v0)]\n" " impl ExampleContract of super::IExampleContract {\n" " // The `set` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" +"inside an implementation marked as `#[abi(embed_v0)]`.\n" " // It can modify the contract's state as it is passed as a " "reference.\n" " fn set(ref self: ContractState, value: u32) {\n" @@ -1019,8 +1019,8 @@ msgid "" " }\n" "\n" " // The `get` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" -" // However, it can't modify the contract's state is passed as a " +"inside an implementation marked as `#[abi(embed_v0)]`.\n" +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn get(self: @ContractState) -> u32 {\n" " // We can call an internal function from any functions within " @@ -1038,7 +1038,7 @@ msgid "" " // The `_read_value` function is outside the implementation that is " "marked as `#[abi(embed_v0)]`, so it's an _internal_ function\n" " // and can only be called from within the contract.\n" -" // However, it can't modify the contract's state is passed as a " +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn _read_value(self: @ContractState) -> u32 {\n" " self.value.read()\n" @@ -1062,14 +1062,14 @@ msgstr "" " }\n" "\n" "\n" -" // The `abi(embed_v0)` attribute indicates that all the functions in " +" // The `#[abi(embed_v0)]` attribute indicates that all the functions in " "this implementation can be called externally.\n" " // Omitting this attribute would make all the functions in this " "implementation internal.\n" " #[abi(embed_v0)]\n" " impl ExampleContract of super::IExampleContract {\n" " // The `set` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" +"inside an implementation marked as `#[abi(embed_v0)]`.\n" " // It can modify the contract's state as it is passed as a " "reference.\n" " fn set(ref self: ContractState, value: u32) {\n" @@ -1077,8 +1077,8 @@ msgstr "" " }\n" "\n" " // The `get` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" -" // However, it can't modify the contract's state is passed as a " +"inside an implementation marked as `#[abi(embed_v0)]`.\n" +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn get(self: @ContractState) -> u32 {\n" " // We can call an internal function from any functions within " @@ -1096,7 +1096,7 @@ msgstr "" " // The `_read_value` function is outside the implementation that is " "marked as `#[abi(embed_v0)]`, so it's an _internal_ function\n" " // and can only be called from within the contract.\n" -" // However, it can't modify the contract's state is passed as a " +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn _read_value(self: @ContractState) -> u32 {\n" " self.value.read()\n" @@ -1298,7 +1298,7 @@ msgid "" "hash and the final value is taken `mod2251−256`. You can learn more about " "the contract storage layout in the [Starknet Documentation](https://docs." "starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-" -"storage/#storage_variables)" +"storage/#storage_variables)." msgstr "" "- Son posibles asignaciones key-value más complejas; por ejemplo, podríamos " "usar `LegacyMap::<(ContractAddress, ContractAddress), Felt252>` para crear " @@ -1310,7 +1310,7 @@ msgstr "" "información sobre el diseño de almacenamiento por contrato en la " "[Documentación de Starknet](https://docs.starknet.io/documentation/" "architecture_and_concepts/Smart_Contracts/contract-storage/" -"#storage_variables)" +"#storage_variables)." #: src/ch00/basics/mappings.md:13 msgid "" @@ -4910,12 +4910,12 @@ msgstr "# Match" #: src/ch00/cairo_cheatsheet/match.md:3 msgid "" -"The \"match\" expression in Cairo allows us to control the flow of our code " -"by comparing a felt data type or an enum against various patterns and then " +"The `match` expression in Cairo allows us to control the flow of our code " +"by comparing a `felt252` data type or an enum against various patterns and then " "running specific code based on the pattern that matches.\n" "For example:" msgstr "" -"La expresión \"match\" en Cairo nos permite controlar el flujo de nuestro " +"La expresión `match` en Cairo nos permite controlar el flujo de nuestro " "código comparando un tipo de datos sentido o una enumeración con varios " "patrones y luego ejecutando código específico basado en el patrón que " "coincide.\n" @@ -5158,7 +5158,7 @@ msgid "" " let new_u256: u256 = my_felt252.into();\n" " let new_felt252: felt252 = new_u16.into();\n" "\n" -" //note a usize is smaller than a felt so we use the try_into\n" +" // Note: usize is smaller than felt252, so we use try_into\n" " let new_usize: usize = my_felt252.try_into().unwrap();\n" "```" msgstr "" @@ -5188,7 +5188,7 @@ msgstr "" " let new_u256: u256 = my_felt252.into();\n" " let new_felt252: felt252 = new_u16.into();\n" "\n" -" //note a usize is smaller than a felt so we use the try_into\n" +" // Note: usize is smaller than felt252, so we use try_into\n" " let new_usize: usize = my_felt252.try_into().unwrap();\n" "```" @@ -11071,36 +11071,36 @@ msgstr "" #~ "pasamos por referencia así: `ref self: ContractState`." #~ msgid "" -#~ "// The `abi(embed_v0)` attribute indicates that all the functions in this " +#~ "// The `#[abi(embed_v0)]` attribute indicates that all the functions in this " #~ "implementation can be called externally.\n" #~ " // Omitting this attribute would make all the functions in this " #~ "implementation internal.\n" #~ msgstr "" -#~ "// The `abi(embed_v0)` attribute indicates that all the functions in this " +#~ "// The `#[abi(embed_v0)]` attribute indicates that all the functions in this " #~ "implementation can be called externally.\n" #~ " // Omitting this attribute would make all the functions in this " #~ "implementation internal.\n" #~ msgid "" #~ "// The `set` function can be called externally because it is written " -#~ "inside an implementation marked as `#[external]`.\n" +#~ "inside an implementation marked as `#[abi(embed_v0)]`.\n" #~ " // It can modify the contract's state as it is passed as a " #~ "reference.\n" #~ msgstr "" #~ "// The `set` function can be called externally because it is written " -#~ "inside an implementation marked as `#[external]`.\n" +#~ "inside an implementation marked as `#[abi(embed_v0)]`.\n" #~ " // It can modify the contract's state as it is passed as a " #~ "reference.\n" #~ msgid "" #~ "// The `get` function can be called externally because it is written " -#~ "inside an implementation marked as `#[external]`.\n" -#~ " // However, it can't modify the contract's state is passed as a " +#~ "inside an implementation marked as `#[abi(embed_v0)]`.\n" +#~ " // However, it can't modify the contract's state, as it is passed as a " #~ "snapshot: it is only a \"view\" function.\n" #~ msgstr "" #~ "// The `get` function can be called externally because it is written " -#~ "inside an implementation marked as `#[external]`.\n" -#~ " // However, it can't modify the contract's state is passed as a " +#~ "inside an implementation marked as `#[abi(embed_v0)]`.\n" +#~ " // However, it can't modify the contract's state, as it is passed as a " #~ "snapshot: it is only a \"view\" function.\n" #~ msgid "" @@ -11125,13 +11125,13 @@ msgstr "" #~ "// The `_read_value` function is outside the implementation that is " #~ "marked as `#[abi(embed_v0)]`, so it's an _internal_ function\n" #~ " // and can only be called from within the contract.\n" -#~ " // However, it can't modify the contract's state is passed as a " +#~ " // However, it can't modify the contract's state, as it is passed as a " #~ "snapshot: it is only a \"view\" function.\n" #~ msgstr "" #~ "// The `_read_value` function is outside the implementation that is " #~ "marked as `#[abi(embed_v0)]`, so it's an _internal_ function\n" #~ " // and can only be called from within the contract.\n" -#~ " // However, it can't modify the contract's state is passed as a " +#~ " // However, it can't modify the contract's state, as it is passed as a " #~ "snapshot: it is only a \"view\" function.\n" #~ msgid "" @@ -11421,8 +11421,8 @@ msgstr "" #~ msgstr "" #~ "// Since a felt252 is smaller than a u256, we can use the into() method\n" -#~ msgid "//note a usize is smaller than a felt so we use the try_into\n" -#~ msgstr "//note a usize is smaller than a felt so we use the try_into\n" +#~ msgid "// Note: usize is smaller than felt252, so we use try_into\n" +#~ msgstr "// Note: usize is smaller than felt252, so we use try_into\n" #~ msgid "Modularity: Easily pluggable into multiple contracts." #~ msgstr "Modularidad: Fácilmente conectable a múltiples contratos." diff --git a/po/messages.pot b/po/messages.pot index 749a5ada..d59c19a2 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -291,7 +291,7 @@ msgstr "" #: src/ch00/basics/storage.md:29 msgid "" -"Actually these two contracts have the same underlying sierra program. From " +"Actually these two contracts have the same underlying Sierra program. From " "the compiler's perspective, the storage variables don't exist until they are " "used." msgstr "" @@ -583,7 +583,7 @@ msgstr "" #: src/ch00/basics/visibility-mutability.md:42 msgid "" -"// The `abi(embed_v0)` attribute indicates that all the functions in this " +"// The `#[abi(embed_v0)]` attribute indicates that all the functions in this " "implementation can be called externally.\n" " // Omitting this attribute would make all the functions in this " "implementation internal.\n" @@ -592,7 +592,7 @@ msgstr "" #: src/ch00/basics/visibility-mutability.md:46 msgid "" "// The `set` function can be called externally because it is written inside " -"an implementation marked as `#[external]`.\n" +"an implementation marked as `#[abi(embed_v0)]`.\n" " // It can modify the contract's state as it is passed as a " "reference.\n" msgstr "" @@ -600,8 +600,8 @@ msgstr "" #: src/ch00/basics/visibility-mutability.md:52 msgid "" "// The `get` function can be called externally because it is written inside " -"an implementation marked as `#[external]`.\n" -" // However, it can't modify the contract's state is passed as a " +"an implementation marked as `#[abi(embed_v0)]`.\n" +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" msgstr "" @@ -623,7 +623,7 @@ msgid "" "// The `_read_value` function is outside the implementation that is marked " "as `#[abi(embed_v0)]`, so it's an _internal_ function\n" " // and can only be called from within the contract.\n" -" // However, it can't modify the contract's state is passed as a " +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" msgstr "" @@ -719,7 +719,7 @@ msgid "" "`h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)` where `ℎ` is the " "Pedersen hash and the final value is taken `mod2251−256`. You can learn more " "about the contract storage layout in the [Starknet " -"Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Contracts/contract-storage/#storage_variables)" +"Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Contracts/contract-storage/#storage_variables)." msgstr "" #: src/ch00/basics/mappings.md:28 @@ -1507,8 +1507,8 @@ msgstr "" #: src/ch00/cairo_cheatsheet/match.md:3 msgid "" -"The \"match\" expression in Cairo allows us to control the flow of our code " -"by comparing a felt data type or an enum against various patterns and then " +"The `match` expression in Cairo allows us to control the flow of our code " +"by comparing a `felt252` data type or an enum against various patterns and then " "running specific code based on the pattern that matches. For example:" msgstr "" @@ -1579,7 +1579,7 @@ msgid "// Since a felt252 is smaller than a u256, we can use the into() method\n msgstr "" #: src/ch00/cairo_cheatsheet/type_casting.md:29 -msgid "//note a usize is smaller than a felt so we use the try_into\n" +msgid "// Note: usize is smaller than felt252, so we use try_into\n" msgstr "" #: src/ch01/upgradeable_contract.md:3 diff --git a/po/zh-cn.po b/po/zh-cn.po index 26111fe2..a1b8bb7a 100644 --- a/po/zh-cn.po +++ b/po/zh-cn.po @@ -392,11 +392,11 @@ msgstr "" #: src/ch00/basics/storage.md:29 msgid "" -"> Actually these two contracts have the same underlying sierra program.\n" +"> Actually these two contracts have the same underlying Sierra program.\n" "> From the compiler's perspective, the storage variables don't exist until " "they are used." msgstr "" -"> 实际上,这两个合约的底层 sierra 程序是一样的。\n" +"> 实际上,这两个合约的底层 Sierra 程序是一样的。\n" "> 从编译器的角度来看,存储变量在使用之前是不存在的。" #: src/ch00/basics/storage.md:32 @@ -936,14 +936,14 @@ msgid "" " }\n" "\n" "\n" -" // The `abi(embed_v0)` attribute indicates that all the functions in this " +" // The `#[abi(embed_v0)]` attribute indicates that all the functions in this " "implementation can be called externally.\n" " // Omitting this attribute would make all the functions in this " "implementation internal.\n" " #[abi(embed_v0)]\n" " impl ExampleContract of super::IExampleContract {\n" " // The `set` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" +"inside an implementation marked as `#[abi(embed_v0)]`.\n" " // It can modify the contract's state as it is passed as a " "reference.\n" " fn set(ref self: ContractState, value: u32) {\n" @@ -951,8 +951,8 @@ msgid "" " }\n" "\n" " // The `get` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" -" // However, it can't modify the contract's state is passed as a " +"inside an implementation marked as `#[abi(embed_v0)]`.\n" +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn get(self: @ContractState) -> u32 {\n" " // We can call an internal function from any functions within the " @@ -970,7 +970,7 @@ msgid "" " // The `_read_value` function is outside the implementation that is " "marked as `#[abi(embed_v0)]`, so it's an _internal_ function\n" " // and can only be called from within the contract.\n" -" // However, it can't modify the contract's state is passed as a " +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn _read_value(self: @ContractState) -> u32 {\n" " self.value.read()\n" @@ -994,14 +994,14 @@ msgstr "" " }\n" "\n" "\n" -" // The `abi(embed_v0)` attribute indicates that all the functions in this " +" // The `#[abi(embed_v0)]` attribute indicates that all the functions in this " "implementation can be called externally.\n" " // Omitting this attribute would make all the functions in this " "implementation internal.\n" " #[abi(embed_v0)]\n" " impl ExampleContract of super::IExampleContract {\n" " // The `set` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" +"inside an implementation marked as `#[abi(embed_v0)]`.\n" " // It can modify the contract's state as it is passed as a " "reference.\n" " fn set(ref self: ContractState, value: u32) {\n" @@ -1009,8 +1009,8 @@ msgstr "" " }\n" "\n" " // The `get` function can be called externally because it is written " -"inside an implementation marked as `#[external]`.\n" -" // However, it can't modify the contract's state is passed as a " +"inside an implementation marked as `#[abi(embed_v0)]`.\n" +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn get(self: @ContractState) -> u32 {\n" " // We can call an internal function from any functions within the " @@ -1028,7 +1028,7 @@ msgstr "" " // The `_read_value` function is outside the implementation that is " "marked as `#[abi(embed_v0)]`, so it's an _internal_ function\n" " // and can only be called from within the contract.\n" -" // However, it can't modify the contract's state is passed as a " +" // However, it can't modify the contract's state, as it is passed as a " "snapshot: it is only a \"view\" function.\n" " fn _read_value(self: @ContractState) -> u32 {\n" " self.value.read()\n" @@ -3672,12 +3672,12 @@ msgstr "# 分支" #: src/ch00/cairo_cheatsheet/match.md:3 msgid "" -"The \"match\" expression in Cairo allows us to control the flow of our code " -"by comparing a felt data type or an enum against various patterns and then " +"The `match` expression in Cairo allows us to control the flow of our code " +"by comparing a `felt252` data type or an enum against various patterns and then " "running specific code based on the pattern that matches.\n" "For example:" msgstr "" -"在 Cairo 中,”match” 表达式允许我们通过将 felt 数据类型或枚举与各种模式进行比" +"在 Cairo 中,`match` 表达式允许我们通过将 `felt252` 数据类型或枚举与各种模式进行比" "较,然后根据匹配的模式运行特定的代码来控制代码的流程。例如:" #: src/ch00/cairo_cheatsheet/match.md:6 @@ -3911,7 +3911,7 @@ msgid "" " let new_u256: u256 = my_felt252.into();\n" " let new_felt252: felt252 = new_u16.into();\n" "\n" -" //note a usize is smaller than a felt so we use the try_into\n" +" // Note: usize is smaller than felt252, so we use try_into\n" " let new_usize: usize = my_felt252.try_into().unwrap();\n" "```" msgstr "" diff --git a/src/ch00/basics/bytearrays-strings.md b/src/ch00/basics/bytearrays-strings.md index 7c864065..5b882065 100644 --- a/src/ch00/basics/bytearrays-strings.md +++ b/src/ch00/basics/bytearrays-strings.md @@ -9,11 +9,11 @@ Each character is encoded on 8 bits following the ASCII standard, so it's possib Short strings are declared with single quotes, like this: `'Hello, World!'`. See the [Felt](../cairo_cheatsheet/felt.md) section for more information about short strings with the `felt252` type. -> Notice that any short string only use up to 31 bytes, so it's possible to represent any short string with the `bytes31`. +> Notice that any short string only use up to 31 bytes, so it's possible to represent any short string with `bytes31`. ## ByteArray (Long strings) -The `ByteArray` struct is used to store strings of arbitrary length. It contain a field `data` of type `Array` to store a sequence of short strings. +The `ByteArray` struct is used to store strings of arbitrary length. It contains a field `data` of type `Array` to store a sequence of short strings. ByteArrays are declared with double quotes, like this: `"Hello, World!"`. diff --git a/src/ch00/basics/custom-types-in-entrypoints.md b/src/ch00/basics/custom-types-in-entrypoints.md index 0cac2514..c3bf484d 100644 --- a/src/ch00/basics/custom-types-in-entrypoints.md +++ b/src/ch00/basics/custom-types-in-entrypoints.md @@ -3,9 +3,8 @@ Using custom types in entrypoints requires our type to implement the `Serde` trait. This is because when calling an entrypoint, the input is sent as an array of `felt252` to the entrypoint, and we need to be able to deserialize it into our custom type. Similarly, when returning a custom type from an entrypoint, we need to be able to serialize it into an array of `felt252`. Thankfully, we can just derive the `Serde` trait for our custom type. -The purpose is to only show the capability of using custom types as inputs and outputs in contract calls. -We are not employing getters and setters for managing the contract's state in this example for simplicity. - ```rust {{#rustdoc_include ../../../listings/getting-started/custom_type_serde/src/contract.cairo:contract}} ``` + +> Note: The purpose of this example is to demonstrate the ability to use custom types as inputs and outputs in contract calls. For simplicity, we are not using getters and setters to manage the contract's state. diff --git a/src/ch00/basics/documentation.md b/src/ch00/basics/documentation.md index 41c25f16..ff8a2eb3 100644 --- a/src/ch00/basics/documentation.md +++ b/src/ch00/basics/documentation.md @@ -1,14 +1,14 @@ # Documentation -It's important to take the time to document your code. It will helps developers and users to understand the contract and its functionalities. +It's important to take the time to document your code. It will help developers and users to understand the contract and its functionalities. In Cairo, you can add comments with `//`. -### Best Practices: +### Best Practices Since Cairo 1, the community has adopted a [Rust-like documentation style](https://doc.rust-lang.org/rust-by-example/meta/doc.html). -### Contract Interface: +### Contract Interface In smart contracts, you will often have a trait that defines the contract's interface (with `#[starknet::interface]`). This is the perfect place to include detailed documentation explaining the purpose and functionality of the contract entry points. You can follow this template: @@ -32,8 +32,8 @@ trait IContract { Keep in mind that this should not describe the implementation details of the function, but rather the high-level purpose and functionality of the contract from the perspective of a user. -### Implementation Details: +### Implementation Details -When writing the logic of the contract, you can add comments to describe the technical implementation details of the functions. +When writing the contract logic, you can add comments to describe the technical implementation details of the functions. > Avoid over-commenting: Comments should provide additional value and clarity. diff --git a/src/ch00/basics/errors.md b/src/ch00/basics/errors.md index 73ae940c..31006283 100644 --- a/src/ch00/basics/errors.md +++ b/src/ch00/basics/errors.md @@ -9,11 +9,11 @@ To throw an error, use the `assert` or `panic` functions: If the check fails, an error is thrown along with a specified value, often a message. It's similar to the `require` statement in Solidity. -- `panic` immediately halt the execution with the given error value. - It should be used when the condition to check is complex and for internal errors. It's similar to the `revert` statement in Solidity. - (Use `panic_with_felt252` to be able to directly pass a felt252 as the error value) +- `panic` immediately halts the execution with the given error value. + It should be used for complex condition checks and for internal errors. It's similar to the `revert` statement in Solidity. + You can use `panic_with_felt252` to directly pass a `felt252` as the error value. -The `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!` and `assert_ge!` macros can be used as an `assert` shorthand to compare two values, but **only** in tests. In contract, you should only use the `assert` function. +The `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!` and `assert_ge!` macros can be used as an `assert` shorthand to compare two values, but **only** in tests. In contracts, you should only use the `assert` function. Here's a simple example that demonstrates the use of these functions: diff --git a/src/ch00/basics/events.md b/src/ch00/basics/events.md index 4e0791aa..8b6ca2a5 100644 --- a/src/ch00/basics/events.md +++ b/src/ch00/basics/events.md @@ -1,9 +1,9 @@ # Events Events are a way to emit data from a contract. All events must be defined in the `Event` enum, which must be annotated with the `#[event]` attribute. -An event is defined as a struct that derives the `#[starknet::Event]` trait. The fields of that struct correspond to the data that will be emitted. An event can be indexed for easy and fast access when querying the data at a later time, by adding a `#[key]` attribute to a field member. +An event is defined as a struct that derives the `starknet::Event` trait. The fields of that struct correspond to the data that will be emitted. An event can be indexed for easy and fast access when querying the data at a later time by adding a `#[key]` attribute to a field member. -Here's a simple example of a contract using events that emit an event each time a counter is incremented by the "increment" function: +Here's a simple example of a contract that emits an event each time a counter is incremented by the `increment` function: ```rust {{#rustdoc_include ../../../listings/getting-started/events/src/counter.cairo:contract}} diff --git a/src/ch00/basics/mappings.md b/src/ch00/basics/mappings.md index adf55eca..db0b6825 100644 --- a/src/ch00/basics/mappings.md +++ b/src/ch00/basics/mappings.md @@ -8,8 +8,7 @@ Some additional notes: - More complex key-value mappings are possible, for example we could use `LegacyMap::<(ContractAddress, ContractAddress), felt252>` to create an allowance on an ERC20 token contract. -- In mappings, the address of the value at key `k_1,...,k_n` is `h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)` where `ℎ` is the Pedersen hash and the final value is taken `mod2251−256`. You can learn more about the contract storage layout in the [Starknet Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-storage/#storage_variables) - +- In mappings, the address of the value at key `k_1,...,k_n` is `h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)` where `ℎ` is the Pedersen hash and the final value is taken \\( \bmod {2^{251}} - 256 \\). You can learn more about the contract storage layout in the [Starknet Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-storage/#storage_variables). ```rust {{#rustdoc_include ../../../listings/getting-started/mappings/src/mappings.cairo:contract}} diff --git a/src/ch00/basics/storage.md b/src/ch00/basics/storage.md index fd8b1d1e..b3f7f3e5 100644 --- a/src/ch00/basics/storage.md +++ b/src/ch00/basics/storage.md @@ -15,7 +15,7 @@ You can define [storage variables](./variables.md#storage-variables) in your con {{#rustdoc_include ../../../listings/getting-started/storage/src/contract.cairo:contract}} ``` -> Actually these two contracts have the same underlying sierra program. +> Actually these two contracts have the same underlying Sierra program. > From the compiler's perspective, the storage variables don't exist until they are used. -You can also read about [storing custom types](./storing-custom-types.md) +You can also read about [storing custom types](./storing-custom-types.md). diff --git a/src/ch00/basics/storing-custom-types.md b/src/ch00/basics/storing-custom-types.md index 24cd3c79..ed96f088 100644 --- a/src/ch00/basics/storing-custom-types.md +++ b/src/ch00/basics/storing-custom-types.md @@ -1,6 +1,6 @@ # Storing Custom Types -While native types can be stored in a contract's storage without any additional work, custom types require a bit more work. This is because at compile time, the compiler does not know how to store custom types in storage. To solve this, we need to implement the `Store` trait for our custom type. Hopefully, we can just derive this trait for our custom type - unless it contains arrays or dictionaries. +While native types can be stored in a contract's storage without any additional work, custom types require a bit more work. This is because at compile time, the compiler does not know how to store custom types in storage. To solve this, we need to implement the `Store` trait for our custom type. It is enough to just derive this trait, unless our custom type contains arrays or dictionaries. ```rust {{#rustdoc_include ../../../listings/getting-started/storing_custom_types/src/contract.cairo:contract}} diff --git a/src/ch00/basics/syscalls.md b/src/ch00/basics/syscalls.md index 7a4fc336..cddc5e7f 100644 --- a/src/ch00/basics/syscalls.md +++ b/src/ch00/basics/syscalls.md @@ -7,6 +7,7 @@ Some of the OS functionalities are exposed to smart contracts through the use of Syscalls return a `SyscallResult` which is either `Sucess` of `Failure`, allowing the contract to handle errors. Here's the available syscalls: + - [get_block_hash](#get_block_hash) - [get_execution_info](#get_execution_info) - [call_contract](#call_contract) @@ -91,6 +92,7 @@ pub struct TxInfo { ``` `starknet::info` provides helper functions to access the `ExecutionInfo` fields in a more convenient way: + - `get_execution_info() -> Box` - `get_caller_address() -> ContractAddress` - `get_contract_address() -> ContractAddress` @@ -108,9 +110,9 @@ fn call_contract_syscall( ``` Call a contract at `address` with the given `entry_point_selector` and `calldata`. -Failure can't be caught for this syscall, if the call fails, the whole transaction will revert. +Failure can't be caught for this syscall, and if the call fails, the whole transaction will revert. -This is not the recommended way to call a contract. Instead, use the dispatcher generated from the contract interface as shown in the [Calling other contracts](../interacting/calling_other_contracts.md). +This is not the recommended way to call a contract. Instead, use the dispatcher generated from the contract interface as shown in the [Calling other contracts](../interacting/calling_other_contracts.md) chapter. @@ -206,7 +208,7 @@ This is used for contract upgrades. Here's an example from the [Upgradeable Cont ``` The new class code will only be used for future calls to the contract. -The current transaction containing the `replace_class` syscall will continue to use the old class code. (You can explicitly use the new class code by calling `call_contract` after the `replace_class` syscall in the same transaction) +The current transaction containing the `replace_class` syscall will continue to use the old class code. Note that you can explicitly use the new class code in the same transaction by calling `call_contract` after the `replace_class` syscall. #### storage_read @@ -233,7 +235,7 @@ Similar to `storage_read`, this low-level syscall is used to write the value `va ## Documentation -Syscalls are defined in [`starknet::syscall`](https://github.com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/corelib/src/starknet/syscalls.cairo) +Syscalls are defined in [`starknet::syscall`](https://github.com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/corelib/src/starknet/syscalls.cairo). You can also read the [official documentation page](https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-calls-cairo1/) for more details. @@ -267,5 +269,5 @@ mod gas_costs { } ``` -Specific gas cost are defined in this [file](https://github.com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/crates/cairo-lang-runner/src/casm_run/mod.rs#L333) +Specific gas cost are defined in this [file](https://github.com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/crates/cairo-lang-runner/src/casm_run/mod.rs#L333) --> diff --git a/src/ch00/basics/variables.md b/src/ch00/basics/variables.md index abcab571..ff9a785f 100644 --- a/src/ch00/basics/variables.md +++ b/src/ch00/basics/variables.md @@ -30,7 +30,7 @@ Storage variables are persistent data stored on the blockchain. They can be acce To write or update a storage variable, you need to interact with the contract through an external entrypoint by sending a transaction. -On the other hand, you can read state variables, for free, without any transaction, simply by interacting with a node. +On the other hand, you can read state variables for free, without any transaction, simply by interacting with a node. Here's a simple example of a contract with one storage variable: @@ -42,7 +42,7 @@ Here's a simple example of a contract with one storage variable: Global variables are predefined variables that provide information about the blockchain and the current execution environment. They can be accessed at any time and from anywhere! -In Starknet, you can access global variables by using specific functions from the starknet core library. +In Starknet, you can access global variables by using specific functions from the Starknet core library. For example, the `get_caller_address` function returns the address of the caller of the current transaction, and the `get_contract_address` function returns the address of the current contract. diff --git a/src/ch00/basics/visibility-mutability.md b/src/ch00/basics/visibility-mutability.md index bbaa5f59..c7ec4301 100644 --- a/src/ch00/basics/visibility-mutability.md +++ b/src/ch00/basics/visibility-mutability.md @@ -7,18 +7,18 @@ There are two types of functions in Starknet contracts: - Functions that are accessible externally and can be called by anyone. - Functions that are only accessible internally and can only be called by other functions in the contract. -These functions are also typically divided into two different implementations blocks. The first `impl` block for externally accessible functions is explicitly annotated with an `#[abi(embed_v0)]` attribute. This indicates that all the functions inside this block can be called either as a transaction or as a view function. The second `impl` block for internally accessible functions is not annotated with any attribute, which means that all the functions inside this block are private by default. +These functions are also typically divided into two different implementation blocks. The first `impl` block for externally accessible functions is explicitly annotated with an `#[abi(embed_v0)]` attribute. This indicates that all the functions inside this block can be called either as a transaction or as a view function. The second `impl` block for internally accessible functions is not annotated with any attribute, which means that all the functions inside this block are private by default. ## State Mutability Regardless of whether a function is internal or external, it can either modify the contract's state or not. When we declare functions that interact with storage variables inside a smart contract, we need to explicitly state that we are accessing the `ContractState` by adding it as the first parameter of the function. This can be done in two different ways: -- If we want our function to be able to mutate the state of the contract, we pass it by reference like this: `ref self: ContractState`. -- If we want our function to be read-only and not mutate the state of the contract, we pass it by snapshot like this: `self: @ContractState`. +- If we want our function to be able to mutate the state of the contract, we pass it by reference like this: `ref self: ContractState` +- If we want our function to be read-only and not mutate the state of the contract, we pass it by snapshot like this: `self: @ContractState` -Read-only functions, also called view functions, can be directly called without making a transaction. You can interact with them directly through a RPC node to read the contract's state, and they're free to call! -External functions, that modify the contract's state, on the other side can only be called by making a transaction. +Read-only functions, also called view functions, can be directly called without making a transaction. You can interact with them directly through an RPC node to read the contract's state, and they're free to call! +External functions, that modify the contract's state, on the other hand, can only be called by making a transaction. Internal functions can't be called externally, but the same principle applies regarding state mutability. diff --git a/src/ch00/cairo_cheatsheet/felt.md b/src/ch00/cairo_cheatsheet/felt.md index 1caa710e..6f1bd841 100644 --- a/src/ch00/cairo_cheatsheet/felt.md +++ b/src/ch00/cairo_cheatsheet/felt.md @@ -1,7 +1,7 @@ -# Felt252 +# Felt -Felt252 is a fundamental data type in Cairo from which all other data types are derived. -Felt252 can also be used to store [short string representations](../basics/bytearrays-strings.md#short-strings) with a maximum length of 31 characters. +`felt252` is a fundamental data type in Cairo from which all other data types are derived. +`felt252` can also be used to store [short string representations](../basics/bytearrays-strings.md#short-strings) with a maximum length of 31 characters. For example: diff --git a/src/ch00/cairo_cheatsheet/loop.md b/src/ch00/cairo_cheatsheet/loop.md index 2f3a92de..cdaf96ff 100644 --- a/src/ch00/cairo_cheatsheet/loop.md +++ b/src/ch00/cairo_cheatsheet/loop.md @@ -1,6 +1,7 @@ # `loop` A `loop` specifies a block of code that will run repetitively until a halting condition is encountered. + For example: ```rust diff --git a/src/ch00/cairo_cheatsheet/mapping.md b/src/ch00/cairo_cheatsheet/mapping.md index a177c6c4..b9ece39e 100644 --- a/src/ch00/cairo_cheatsheet/mapping.md +++ b/src/ch00/cairo_cheatsheet/mapping.md @@ -1,6 +1,6 @@ -# Mapping +# `LegacyMap` -The ```LegacyMap``` type can be used to represent a collection of key-value. +The `LegacyMap` type can be used to represent a collection of key-value. ```rust {{#include ../../../listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo}} diff --git a/src/ch00/cairo_cheatsheet/match.md b/src/ch00/cairo_cheatsheet/match.md index dfb127b9..d5a7b2f4 100644 --- a/src/ch00/cairo_cheatsheet/match.md +++ b/src/ch00/cairo_cheatsheet/match.md @@ -1,6 +1,7 @@ # Match -The "match" expression in Cairo allows us to control the flow of our code by comparing a felt data type or an enum against various patterns and then running specific code based on the pattern that matches. +The `match` expression in Cairo allows us to control the flow of our code by comparing a `felt252` data type or an enum against various patterns and then running specific code based on the pattern that matches. + For example: ```rust diff --git a/src/ch00/cairo_cheatsheet/struct.md b/src/ch00/cairo_cheatsheet/struct.md index d5fe295f..768c813a 100644 --- a/src/ch00/cairo_cheatsheet/struct.md +++ b/src/ch00/cairo_cheatsheet/struct.md @@ -1,6 +1,7 @@ # Struct -A struct is a data type similar to tuple. Like tuples they can be used to hold data of different types. +A struct is a data type similar to a tuple. Like tuples, they can be used to hold data of different types. + For example: ```rust diff --git a/src/ch00/cairo_cheatsheet/tuples.md b/src/ch00/cairo_cheatsheet/tuples.md index 2b82bd43..b3218b67 100644 --- a/src/ch00/cairo_cheatsheet/tuples.md +++ b/src/ch00/cairo_cheatsheet/tuples.md @@ -1,6 +1,7 @@ # Tuples Tuples is a data type to group a fixed number of items of potentially different types into a single compound structure. Unlike arrays, tuples have a set length and can contain elements of varying types. Once a tuple is created, its size cannot change. + For example: ```rust diff --git a/src/ch00/cairo_cheatsheet/type_casting.md b/src/ch00/cairo_cheatsheet/type_casting.md index f6a143d9..7e78d891 100644 --- a/src/ch00/cairo_cheatsheet/type_casting.md +++ b/src/ch00/cairo_cheatsheet/type_casting.md @@ -1,7 +1,8 @@ # Type casting -Cairo supports the conversion from one scalar types to another by using the into and try_into methods. -`traits::Into` is used for conversion from a smaller data type to a larger data type, while `traits::TryInto` is used when converting from a larger to a smaller type that might not fit. +Cairo supports the conversion from one scalar type to another by using the `into` and `try_into` methods. +The `into` method is used for conversion from a smaller data type to a larger data type, while `try_into` is used when converting from a larger to a smaller type that might not fit. + For example: ```rust diff --git a/src/ch00/cairo_cheatsheet/while.md b/src/ch00/cairo_cheatsheet/while.md index 97cb1bed..51c91602 100644 --- a/src/ch00/cairo_cheatsheet/while.md +++ b/src/ch00/cairo_cheatsheet/while.md @@ -8,4 +8,5 @@ A `while` loop allows you to specify a condition that must be true for the loop ### See also -[loop](loop.md) +- [loop](loop.md) +- [while let](while_let.md) diff --git a/src/ch00/cairo_cheatsheet/while_let.md b/src/ch00/cairo_cheatsheet/while_let.md index 4f9c4caa..90eda4b3 100644 --- a/src/ch00/cairo_cheatsheet/while_let.md +++ b/src/ch00/cairo_cheatsheet/while_let.md @@ -8,4 +8,5 @@ A `while let` loop is a combination of a `while` loop and a `let` statement. It ### See also -[while](while.md) +- [while](while.md) +- [if let](if_let.md) diff --git a/src/ch00/interacting/calling_other_contracts.md b/src/ch00/interacting/calling_other_contracts.md index 62f02078..c9b3d89c 100644 --- a/src/ch00/interacting/calling_other_contracts.md +++ b/src/ch00/interacting/calling_other_contracts.md @@ -3,9 +3,9 @@ There are two different ways to call other contracts in Cairo. The easiest way to call other contracts is by using the dispatcher of the contract you want to call. -You can read more about Dispatchers in the [Cairo Book](https://book.cairo-lang.org/ch99-02-02-contract-dispatcher-library-dispatcher-and-system-calls.html#contract-dispatcher) +You can read more about Dispatchers in the [Cairo Book](https://book.cairo-lang.org/ch99-02-02-contract-dispatcher-library-dispatcher-and-system-calls.html#contract-dispatcher). -The other way is to use the `starknet::call_contract_syscall` syscall yourself. However, this method is not recommended and will not be covered in this example. +The other way is to use the `starknet::call_contract_syscall` syscall yourself. However, this method is not recommended and will not be covered in this chapter. In order to call other contracts using dispatchers, you will need to define the called contract's interface as a trait annotated with the `#[starknet::interface]` attribute, and then import the `IContractDispatcher` and `IContractDispatcherTrait` items in your contract. @@ -15,7 +15,7 @@ Here's the `Callee` contract interface and implementation: {{#rustdoc_include ../../../listings/getting-started/calling_other_contracts/src/caller.cairo:callee_contract}} ``` -The following `Caller` contract use the `Callee` interface to call the `Callee` contract: +The following `Caller` contract uses the `Callee` dispatcher to call the `Callee` contract: ```rust {{#rustdoc_include ../../../listings/getting-started/calling_other_contracts/src/caller.cairo:caller_contract}} diff --git a/src/ch00/interacting/factory.md b/src/ch00/interacting/factory.md index 4d5c8baa..76e503a6 100644 --- a/src/ch00/interacting/factory.md +++ b/src/ch00/interacting/factory.md @@ -1,8 +1,8 @@ # Factory Pattern -The factory pattern is a well known pattern in object oriented programming. It provides an abstraction on how to instantiate a class. +The factory pattern is a well known pattern in object oriented programming. It provides an abstraction on how to instantiate a class. -In the case of smart contracts, we can use this pattern by defining a factory contract that have the sole responsibility of creating and managing other contracts. +In the case of smart contracts, we can use this pattern by defining a factory contract that has the sole responsibility of creating and managing other contracts. ## Class hash and contract instance @@ -14,7 +14,7 @@ Using the factory pattern, we can deploy multiple instances of the same contract ## Minimal example -Here's a minimal example of a factory contract that deploy the `SimpleCounter` contract: +Here's a minimal example of a factory contract that deploys the `SimpleCounter` contract: ```rust {{#rustdoc_include ../../../listings/getting-started/factory/src/simple_factory.cairo:contract}} @@ -24,6 +24,6 @@ This factory can be used to deploy multiple instances of the `SimpleCounter` con The `SimpleCounter` class hash is stored inside the factory, and can be upgraded with the `update_counter_class_hash` function which allows to reuse the same factory contract when the `SimpleCounter` contract is upgraded. -This minimal example lacks several useful features such as access control, tracking of deployed contracts, events, ... +> Note: This minimal example lacks several useful features such as access control, tracking of deployed contracts, events etc. diff --git a/src/ch00/interacting/interfaces-traits.md b/src/ch00/interacting/interfaces-traits.md index f9b49cb7..54a2e591 100644 --- a/src/ch00/interacting/interfaces-traits.md +++ b/src/ch00/interacting/interfaces-traits.md @@ -2,15 +2,15 @@ Contract interfaces define the structure and behavior of a contract, serving as the contract's public ABI. They list all the function signatures that a contract exposes. For a detailed explanation of interfaces, you can refer to the [Cairo Book](https://book.cairo-lang.org/ch99-01-02-a-simple-contract.html). -In cairo, to specify the interface you need to define a trait annotated with `#[starknet::interface]` and then implement that trait in the contract. +In Cairo, to specify the interface you need to define a trait annotated with `#[starknet::interface]` and then implement that trait in the contract. When a function needs to access the contract state, it must have a `self` parameter of type `ContractState`. This implies that the corresponding function signature in the interface trait must also take a `TContractState` type as a parameter. It's important to note that every function in the contract interface must have this `self` parameter of type `TContractState`. -You can use the `#[generate_trait]` attribute to implicitly generate the trait for a specific implementation block. This attribute automatically generates a trait with the same functions as the ones in the implemented block, replacing the `self` parameter with a generic `TContractState` parameter. However, you will need to annotate the block with the `#[abi(per_item)]` attribute, and each function with the appropriate attribute depending on whether it's an external function, a constructor or a l1 handler. +You can use the `#[generate_trait]` attribute to implicitly generate the trait for a specific implementation block. This attribute automatically generates a trait with the same functions as the ones in the implemented block, replacing the `self` parameter with a generic `TContractState` parameter. However, you will need to annotate the block with the `#[abi(per_item)]` attribute, and each function with the appropriate attribute depending on whether it's an external function, a constructor or an L1 handler. In summary, there's two ways to handle interfaces: -- Explicitly, by defining a trait annoted with `#[starknet::interface]` +- Explicitly, by defining a trait annotated with `#[starknet::interface]` - Implicitly, by using `#[generate_trait]` combined with the `#[abi(per_item)]` attributes, and annotating each function inside the implementation block with the appropriate attribute. ## Explicit interface diff --git a/src/ch00/testing/contract-testing.md b/src/ch00/testing/contract-testing.md index 5d9c3e0c..ae51404a 100644 --- a/src/ch00/testing/contract-testing.md +++ b/src/ch00/testing/contract-testing.md @@ -20,16 +20,16 @@ Each test is defined as a function with the `#[test]` attribute. You can also ch As we are in the context of a smart contract, you can also set up the gas limit for a test by using the `#[available_gas(X)]`. This is a great way to ensure that some of your contract's features stay under a certain gas limit! -> Note: The term "gas" here refers to Sierra gas, not L1 gas +> Note: The term "gas" here refers to Sierra gas, not L1 gas. Now, let's move on to the testing process: -- Use the `deploy` function logic to declare and deploy your contract. -- Use `assert` to verify that the contract behaves as expected in the given context. - - You can also use assertions macros: `assert_eq`, `assert_ne`, `assert_gt`, `assert_ge`, `assert_lt`, `assert_le` +- Use the `deploy` function logic to declare and deploy your contract +- Use `assert` to verify that the contract behaves as expected in the given context + - You can also use assertion macros: `assert_eq!`, `assert_ne!`, `assert_gt!`, `assert_ge!`, `assert_lt!`, `assert_le!` -If you didn't noticed yet, every examples in this book have hidden tests, you can see them by clicking on the "Show hidden lines" (eyes icon) on the top right of code blocks. -You can also find a detailed explanation of testing in cairo in the [Cairo book - Chapter 10](https://book.cairo-lang.org/ch10-00-testing-cairo-programs.html). +If you haven't noticed yet, every example in this book has hidden tests, you can see them by clicking the "Show hidden lines" (eyes icon) on the top right of code blocks. +You can also find a detailed explanation of testing in Cairo in [The Cairo Book](https://book.cairo-lang.org/ch10-00-testing-cairo-programs.html). ## Using the contract state @@ -82,12 +82,12 @@ You may also need the `info` module from the corelib, which allows you to access - `get_block_timestamp() -> u64` - `get_block_number() -> u64` -You can found the full list of functions in the [Starknet Corelib repo](https://github.com/starkware-libs/cairo/tree/main/corelib/src/starknet). +You can find the full list of functions in the [Starknet Corelib repo](https://github.com/starkware-libs/cairo/tree/main/corelib/src/starknet). ## Starknet Foundry Starknet Foundry is a powerful toolkit for developing smart contracts on Starknet. It offers support for testing Starknet smart contracts on top of `scarb` with the `Forge` tool. -Testing with `snforge` is similar to the process we just described but simplified. Moreover, additional features are on the way, including cheatcodes or parallel tests execution. We highly recommend exploring Starknet Foundry and incorporating it into your projects. +Testing with `snforge` is similar to the process we just described, but simplified. Moreover, additional features are on the way, including cheatcodes and parallel test execution. We highly recommend exploring Starknet Foundry and incorporating it into your projects. For more detailed information about testing contracts with Starknet Foundry, check out the [Starknet Foundry Book - Testing Contracts](https://foundry-rs.github.io/starknet-foundry/testing/contracts.html). diff --git a/src/starknet-by-example.md b/src/starknet-by-example.md index 8ab3b71a..962109c8 100644 --- a/src/starknet-by-example.md +++ b/src/starknet-by-example.md @@ -2,7 +2,7 @@ Starknet By Example is a collection of examples of how to use the Cairo programming language to create smart contracts on Starknet. -Starknet is a permissionless Validity-Rollup that supports general computation. It is currently used as an Ethereum layer-2. Starknet use the STARK cryptographic proof system to ensure high safety and scalability. +Starknet is a permissionless Validity-Rollup that supports general computation. It is currently used as an Ethereum layer-2. Starknet uses the STARK cryptographic proof system to ensure high safety and scalability. Starknet smart contracts are written in the Cairo language. Cairo is a Turing-complete programming language designed to write provable programs, abstracting the zk-STARK proof system away from the programmer. @@ -20,9 +20,9 @@ The later chapters will cover more advanced topics and show you how to write mor Each chapter is a standalone example that demonstrates a specific feature or common use case of smart contracts on Starknet. If you are new to Starknet, it is recommended to read the chapters in order. -Most examples contains interfaces and tests that are hidden by default. You can hover over the code blocks and click on the "Show hidden lines" (eyes icon) to see the hidden code. +Most examples contain interfaces and tests that are hidden by default. You can hover over the code blocks and click on the "Show hidden lines" (eyes icon) to see the hidden code. -You can run each examples online by using the [Starknet Remix Plugin](https://remix.ethereum.org/?#activate=Starknet). +You can run each example online by using the [Starknet Remix Plugin](https://remix.ethereum.org/?#activate=Starknet). ## Further reading @@ -33,7 +33,8 @@ For more resources, check [Awesome Starknet](https://github.com/keep-starknet-st ## Versions -The current version of this book use: +The current version this book uses: + ``` cairo 2.6.3 edition = '2023_11' From ab9da414652e9c975d7be503287927e7ffa5b7ca Mon Sep 17 00:00:00 2001 From: Asher <141028690+No-bodyq@users.noreply.github.com> Date: Tue, 4 Jun 2024 10:26:00 +0100 Subject: [PATCH 09/36] component dependencies test (#198) * test: component dependencies * undo changes * test: countable_component * test: countable_internal_dep_switch * fix: apply requested changes * fix: small fmt fix --------- Co-authored-by: julio4 --- .../src/countable_dep_switch.cairo | 65 +++++++++++++++++++ .../src/countable_internal_dep_switch.cairo | 4 ++ src/components/dependencies.md | 2 +- 3 files changed, 70 insertions(+), 1 deletion(-) diff --git a/listings/applications/components_dependencies/src/countable_dep_switch.cairo b/listings/applications/components_dependencies/src/countable_dep_switch.cairo index c27abd55..762470e5 100644 --- a/listings/applications/components_dependencies/src/countable_dep_switch.cairo +++ b/listings/applications/components_dependencies/src/countable_dep_switch.cairo @@ -1,3 +1,4 @@ +// ANCHOR: contract #[starknet::component] pub mod countable_component { use components::countable::ICountable; @@ -25,3 +26,67 @@ pub mod countable_component { } // ANCHOR_END: impl } + +//ANCHOR_END: contract + +#[starknet::contract] +mod MockContract { + use super::countable_component; + use components::switchable::ISwitchable; + + component!(path: countable_component, storage: counter, event: CountableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + counter: countable_component::Storage, + switch: bool, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + CountableEvent: countable_component::Event, + } + + #[abi(embed_v0)] + impl CountableImpl = countable_component::Countable; + #[abi(embed_v0)] + impl Switchable of ISwitchable { + fn switch(ref self: ContractState) {} + + fn is_on(self: @ContractState) -> bool { + true + } + } +} + + +#[cfg(test)] +mod test { + use super::MockContract; + use components::countable::{ICountableDispatcher, ICountableDispatcherTrait}; + use starknet::syscalls::deploy_syscall; + use starknet::SyscallResultTrait; + + fn deploy_countable() -> ICountableDispatcher { + let (contract_address, _) = deploy_syscall( + MockContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ICountableDispatcher { contract_address: contract_address } + } + + #[test] + fn test_get() { + let countable = deploy_countable(); + assert_eq!(countable.get(), 0); + } + + #[test] + fn test_increment() { + let countable = deploy_countable(); + countable.increment(); + assert_eq!(countable.get(), 1); + } +} diff --git a/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo b/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo index 640309a7..bfa5e020 100644 --- a/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo +++ b/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo @@ -1,3 +1,4 @@ +//ANCHOR: contract #[starknet::component] pub mod countable_component { use components::countable::ICountable; @@ -57,3 +58,6 @@ pub mod countable_component { } } } +//ANCHOR_END: contract + + diff --git a/src/components/dependencies.md b/src/components/dependencies.md index 59d957ea..42a363f8 100644 --- a/src/components/dependencies.md +++ b/src/components/dependencies.md @@ -55,7 +55,7 @@ We make the `Countable` component depend on the `Switchable` component. This will allow to do `switchable::ComponentState` -> `TContractState` -> `countable::ComponentState` and access the internal functions of the `Switchable` component inside the `Countable` component: ```rust -{{#rustdoc_include ../../listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo}} +{{#rustdoc_include ../../listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo:contract}} ``` The contract remains the same that the previous example, but the implementation of the `Countable` component is different: From 8720e01f1e3cfbefff587682b7c1b8b145d7c687 Mon Sep 17 00:00:00 2001 From: hudem1 <55464342+hudem1@users.noreply.github.com> Date: Tue, 4 Jun 2024 18:30:23 +0900 Subject: [PATCH 10/36] feat(staking): Add staking contract example (#202) * feat(staking): first draft with contract and tests Missing events and some tests * feat(staking): Add events and events-related tests * feat(staking): Add a more complex test for rewards set up * feat(staking): Add md file * feat(staking): Apply changes according to PR review --- Scarb.lock | 7 + listings/applications/staking/.gitignore | 1 + listings/applications/staking/Scarb.toml | 13 + .../applications/staking/src/contract.cairo | 269 +++++++++ listings/applications/staking/src/lib.cairo | 7 + .../staking/src/tests/staking_tests.cairo | 562 ++++++++++++++++++ .../staking/src/tests/tokens.cairo | 59 ++ src/SUMMARY.md | 1 + src/ch01/staking.md | 31 + 9 files changed, 950 insertions(+) create mode 100644 listings/applications/staking/.gitignore create mode 100644 listings/applications/staking/Scarb.toml create mode 100644 listings/applications/staking/src/contract.cairo create mode 100644 listings/applications/staking/src/lib.cairo create mode 100644 listings/applications/staking/src/tests/staking_tests.cairo create mode 100644 listings/applications/staking/src/tests/tokens.cairo create mode 100644 src/ch01/staking.md diff --git a/Scarb.lock b/Scarb.lock index 7dab0bb1..81ae93eb 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -101,6 +101,13 @@ version = "0.1.0" name = "simple_vault" version = "0.1.0" +[[package]] +name = "staking" +version = "0.1.0" +dependencies = [ + "openzeppelin", +] + [[package]] name = "storage" version = "0.1.0" diff --git a/listings/applications/staking/.gitignore b/listings/applications/staking/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/applications/staking/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/applications/staking/Scarb.toml b/listings/applications/staking/Scarb.toml new file mode 100644 index 00000000..7ac644c6 --- /dev/null +++ b/listings/applications/staking/Scarb.toml @@ -0,0 +1,13 @@ +[package] +name = "staking" +version.workspace = true +edition = '2023_11' + +[dependencies] +starknet.workspace = true +openzeppelin.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] diff --git a/listings/applications/staking/src/contract.cairo b/listings/applications/staking/src/contract.cairo new file mode 100644 index 00000000..e158c8eb --- /dev/null +++ b/listings/applications/staking/src/contract.cairo @@ -0,0 +1,269 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IStakingContract { + fn set_reward_amount(ref self: TContractState, amount: u256); + fn set_reward_duration(ref self: TContractState, duration: u256); + fn stake(ref self: TContractState, amount: u256); + fn withdraw(ref self: TContractState, amount: u256); + fn get_rewards(self: @TContractState, account: ContractAddress) -> u256; + fn claim_rewards(ref self: TContractState); +} + +mod Errors { + pub const NULL_REWARDS: felt252 = 'Reward amount must be > 0'; + pub const NOT_ENOUGH_REWARDS: felt252 = 'Reward amount must be > balance'; + pub const NULL_AMOUNT: felt252 = 'Amount must be > 0'; + pub const NULL_DURATION: felt252 = 'Duration must be > 0'; + pub const UNFINISHED_DURATION: felt252 = 'Reward duration not finished'; + pub const NOT_OWNER: felt252 = 'Caller is not the owner'; + pub const NOT_ENOUGH_BALANCE: felt252 = 'Balance too low'; +} + +#[starknet::contract] +pub mod StakingContract { + use core::starknet::event::EventEmitter; + use core::traits::Into; + use core::num::traits::Zero; + use starknet::{ContractAddress, get_caller_address, get_block_timestamp, get_contract_address}; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + + #[storage] + struct Storage { + staking_token: IERC20Dispatcher, + reward_token: IERC20Dispatcher, + owner: ContractAddress, + reward_rate: u256, + duration: u256, + current_reward_per_staked_token: u256, + finish_at: u256, + // last time an operation (staking / withdrawal / rewards claimed) was registered + last_updated_at: u256, + last_user_reward_per_staked_token: LegacyMap::, + unclaimed_rewards: LegacyMap::, + total_distributed_rewards: u256, + // total amount of staked tokens + total_supply: u256, + // amount of staked tokens per user + balance_of: LegacyMap::, + } + + #[event] + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + Deposit: Deposit, + Withdrawal: Withdrawal, + RewardsFinished: RewardsFinished, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Deposit { + pub user: ContractAddress, + pub amount: u256, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Withdrawal { + pub user: ContractAddress, + pub amount: u256, + } + + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct RewardsFinished { + pub msg: felt252, + } + + #[constructor] + fn constructor( + ref self: ContractState, + staking_token_address: ContractAddress, + reward_token_address: ContractAddress, + ) { + self.staking_token.write(IERC20Dispatcher { contract_address: staking_token_address }); + self.reward_token.write(IERC20Dispatcher { contract_address: reward_token_address }); + + self.owner.write(get_caller_address()); + } + + #[abi(embed_v0)] + impl StakingContract of super::IStakingContract { + fn set_reward_duration(ref self: ContractState, duration: u256) { + self.only_owner(); + + assert(duration > 0, super::Errors::NULL_DURATION); + + // can only set duration if the previous duration has already finished + assert( + self.finish_at.read() < get_block_timestamp().into(), + super::Errors::UNFINISHED_DURATION + ); + + self.duration.write(duration); + } + + fn set_reward_amount(ref self: ContractState, amount: u256) { + self.only_owner(); + self.update_rewards(Zero::zero()); + + assert(amount > 0, super::Errors::NULL_REWARDS); + assert(self.duration.read() > 0, super::Errors::NULL_DURATION); + + let block_timestamp: u256 = get_block_timestamp().into(); + + let rate = if self.finish_at.read() < block_timestamp { + amount / self.duration.read() + } else { + let remaining_rewards = self.reward_rate.read() + * (self.finish_at.read() - block_timestamp); + (remaining_rewards + amount) / self.duration.read() + }; + + assert( + self.reward_token.read().balance_of(get_contract_address()) >= rate + * self.duration.read(), + super::Errors::NOT_ENOUGH_REWARDS + ); + + self.reward_rate.write(rate); + + // even if the previous reward duration was not finished, we reset the finish_at variable + self.finish_at.write(block_timestamp + self.duration.read()); + self.last_updated_at.write(block_timestamp); + + // reset total distributed rewards + self.total_distributed_rewards.write(0); + } + + fn stake(ref self: ContractState, amount: u256) { + assert(amount > 0, super::Errors::NULL_AMOUNT); + + let user = get_caller_address(); + self.update_rewards(user); + + self.balance_of.write(user, self.balance_of.read(user) + amount); + self.total_supply.write(self.total_supply.read() + amount); + self.staking_token.read().transfer_from(user, get_contract_address(), amount); + + self.emit(Deposit { user, amount }); + } + + fn withdraw(ref self: ContractState, amount: u256) { + assert(amount > 0, super::Errors::NULL_AMOUNT); + + let user = get_caller_address(); + + assert( + self.staking_token.read().balance_of(user) >= amount, + super::Errors::NOT_ENOUGH_BALANCE + ); + + self.update_rewards(user); + + self.balance_of.write(user, self.balance_of.read(user) - amount); + self.total_supply.write(self.total_supply.read() - amount); + self.staking_token.read().transfer(user, amount); + + self.emit(Withdrawal { user, amount }); + } + + fn get_rewards(self: @ContractState, account: ContractAddress) -> u256 { + self.unclaimed_rewards.read(account) + self.compute_new_rewards(account) + } + + fn claim_rewards(ref self: ContractState) { + let user = get_caller_address(); + self.update_rewards(user); + + let rewards = self.unclaimed_rewards.read(user); + + if rewards > 0 { + self.unclaimed_rewards.write(user, 0); + self.reward_token.read().transfer(user, rewards); + } + } + } + + #[generate_trait] + impl PrivateFunctions of PrivateFunctionsTrait { + // call this function every time a user (including owner) performs a state-modifying action + fn update_rewards(ref self: ContractState, account: ContractAddress) { + self + .current_reward_per_staked_token + .write(self.compute_current_reward_per_staked_token()); + + self.last_updated_at.write(self.last_time_applicable()); + + if account.is_non_zero() { + self.distribute_user_rewards(account); + + self + .last_user_reward_per_staked_token + .write(account, self.current_reward_per_staked_token.read()); + + self.send_rewards_finished_event(); + } + } + + fn distribute_user_rewards(ref self: ContractState, account: ContractAddress) { + // compute earned rewards since last update for the user `account` + let user_rewards = self.get_rewards(account); + self.unclaimed_rewards.write(account, user_rewards); + + // track amount of total rewards distributed + self + .total_distributed_rewards + .write(self.total_distributed_rewards.read() + user_rewards); + } + + fn send_rewards_finished_event(ref self: ContractState) { + // check if we send a RewardsFinished event + if self.last_updated_at.read() == self.finish_at.read() { + let total_rewards = self.reward_rate.read() * self.duration.read(); + + if total_rewards != 0 && self.total_distributed_rewards.read() == total_rewards { + // owner should set up NEW rewards into the contract + self.emit(RewardsFinished { msg: 'Rewards all distributed' }); + } else { + // owner should set up rewards into the contract (or add duration by setting up rewards) + self.emit(RewardsFinished { msg: 'Rewards not active yet' }); + } + } + } + + fn compute_current_reward_per_staked_token(self: @ContractState) -> u256 { + if self.total_supply.read() == 0 { + self.current_reward_per_staked_token.read() + } else { + self.current_reward_per_staked_token.read() + + self.reward_rate.read() + * (self.last_time_applicable() - self.last_updated_at.read()) + / self.total_supply.read() + } + } + + fn compute_new_rewards(self: @ContractState, account: ContractAddress) -> u256 { + self.balance_of.read(account) + * (self.current_reward_per_staked_token.read() + - self.last_user_reward_per_staked_token.read(account)) + } + + #[inline(always)] + fn last_time_applicable(self: @ContractState) -> u256 { + PrivateFunctions::min(self.finish_at.read(), get_block_timestamp().into()) + } + + #[inline(always)] + fn min(x: u256, y: u256) -> u256 { + if (x <= y) { + x + } else { + y + } + } + + fn only_owner(self: @ContractState) { + let caller = get_caller_address(); + assert(caller == self.owner.read(), super::Errors::NOT_OWNER); + } + } +} diff --git a/listings/applications/staking/src/lib.cairo b/listings/applications/staking/src/lib.cairo new file mode 100644 index 00000000..0d46ef57 --- /dev/null +++ b/listings/applications/staking/src/lib.cairo @@ -0,0 +1,7 @@ +mod contract; + +#[cfg(test)] +mod tests { + mod tokens; + mod staking_tests; +} diff --git a/listings/applications/staking/src/tests/staking_tests.cairo b/listings/applications/staking/src/tests/staking_tests.cairo new file mode 100644 index 00000000..d1004867 --- /dev/null +++ b/listings/applications/staking/src/tests/staking_tests.cairo @@ -0,0 +1,562 @@ +mod tests { + use staking::contract::StakingContract::__member_module_unclaimed_rewards::InternalContractMemberStateTrait as UncRewInt; + use staking::contract::StakingContract::__member_module_last_updated_at::InternalContractMemberStateTrait as LastUpInt; + use staking::contract::StakingContract::__member_module_finish_at::InternalContractMemberStateTrait as FinInt; + use staking::contract::StakingContract::__member_module_reward_rate::InternalContractMemberStateTrait as RewRateInt; + use staking::contract::StakingContract::__member_module_total_supply::InternalContractMemberStateTrait as TotSupInt; + use staking::contract::StakingContract::__member_module_balance_of::InternalContractMemberStateTrait as BalInt; + use staking::contract::StakingContract::__member_module_staking_token::InternalContractMemberStateTrait as StakeTokInt; + use staking::contract::StakingContract::__member_module_reward_token::InternalContractMemberStateTrait as RewardTokInt; + use core::traits::TryInto; + use openzeppelin::token::erc20::interface::IERC20DispatcherTrait; + use openzeppelin::token::erc20::erc20::ERC20Component::InternalTrait; + use staking::contract::IStakingContractDispatcherTrait; + use staking::tests::tokens::{RewardToken, StakingToken}; + use staking::contract::{ + StakingContract, IStakingContractDispatcher, StakingContract::ownerContractMemberStateTrait, + StakingContract::Event, StakingContract::Deposit, StakingContract::Withdrawal, + StakingContract::RewardsFinished + }; + use openzeppelin::token::erc20::{interface::IERC20Dispatcher}; + use starknet::syscalls::deploy_syscall; + use starknet::SyscallResultTrait; + use core::serde::Serde; + use starknet::testing::{set_caller_address, set_contract_address, set_block_timestamp, pop_log}; + use starknet::{contract_address_const, ContractAddress, get_contract_address}; + + #[derive(Copy, Drop)] + struct Deployment { + contract: IStakingContractDispatcher, + staking_token: IERC20Dispatcher, + reward_token: IERC20Dispatcher + } + + fn deploy_util(class_hash: felt252, calldata: Array) -> ContractAddress { + let (address, _) = deploy_syscall(class_hash.try_into().unwrap(), 0, calldata.span(), false) + .unwrap_syscall(); + + address + } + + fn deploy_erc20( + class_hash: felt252, name: ByteArray, symbol: ByteArray + ) -> (ContractAddress, IERC20Dispatcher) { + let mut call_data: Array = ArrayTrait::new(); + Serde::serialize(@name, ref call_data); + Serde::serialize(@symbol, ref call_data); + + let address = deploy_util(class_hash, call_data); + (address, IERC20Dispatcher { contract_address: address }) + } + + fn deploy_staking_contract( + staking_token_address: ContractAddress, reward_token_address: ContractAddress + ) -> (ContractAddress, IStakingContractDispatcher) { + let mut calldata: Array = array![]; + calldata.append(staking_token_address.into()); + calldata.append(reward_token_address.into()); + + let staking_contract_address = deploy_util(StakingContract::TEST_CLASS_HASH, calldata); + ( + staking_contract_address, + IStakingContractDispatcher { contract_address: staking_contract_address } + ) + } + + fn setup() -> Deployment { + let (staking_token_address, staking_token) = deploy_erc20( + StakingToken::TEST_CLASS_HASH, "StakingToken", "StakingTKN" + ); + let (reward_token_address, reward_token) = deploy_erc20( + RewardToken::TEST_CLASS_HASH, "RewardToken", "RewardTKN" + ); + + let (_, staking_contract) = deploy_staking_contract( + staking_token_address, reward_token_address + ); + + Deployment { contract: staking_contract, staking_token, reward_token } + } + + fn mint_and_approve_staking_tokens_to( + recipient: ContractAddress, amount: u256, deploy: Deployment, value_to_approve: u256 + ) { + // mint tokens + let mut state = StakingToken::contract_state_for_testing(); + // pretend as if we were in the deployed staking token contract + set_contract_address(deploy.staking_token.contract_address); + state.erc20._mint(recipient, amount); + + // approve staking contract to spend user's tokens + set_contract_address(recipient); + deploy.staking_token.approve(deploy.contract.contract_address, value_to_approve); + } + + fn mint_reward_tokens_to( + deployed_contract: ContractAddress, amount: u256, reward_token_address: ContractAddress + ) { + // mint tokens + let mut state = RewardToken::contract_state_for_testing(); + // pretend as if we were in the deployed reward token contract + set_contract_address(reward_token_address); + state.erc20._mint(deployed_contract, amount); + } + + #[test] + #[available_gas(20000000)] + fn should_deploy() { + /// setup + let owner = contract_address_const::<'owner'>(); + set_contract_address(owner); + + /// when + let deploy = setup(); + + // "link" a new StakingContract struct to the deployed StakingContract contract + // in order to access its internal state fields for assertions + let state = StakingContract::contract_state_for_testing(); + // pretend as if we were in the deployed contract + set_contract_address(deploy.contract.contract_address); + + /// then + assert(state.owner.read() == owner, 'wrong owner'); + assert( + state.staking_token.read().contract_address == deploy.staking_token.contract_address, + 'wrong staking token contract' + ); + assert( + state.reward_token.read().contract_address == deploy.reward_token.contract_address, + 'wrong reward token contract' + ); + } + + #[test] + #[available_gas(20000000)] + fn stake_and_withdraw_succeed() { + /// set up + + // deploy + let owner = contract_address_const::<'owner'>(); + set_contract_address(owner); + let deploy = setup(); + + // mint staking tokens to a user + let user = contract_address_const::<'user'>(); + let stake_amount = 40; + let amount_tokens_minted = 100; + mint_and_approve_staking_tokens_to(user, amount_tokens_minted, deploy, stake_amount); + + /// when - staking + set_contract_address(user); + deploy.contract.stake(stake_amount); + + /// then + // so far the user has 60 tokens left and staked 40 tokens + let state = StakingContract::contract_state_for_testing(); + set_contract_address(deploy.contract.contract_address); + assert(state.balance_of.read(user) == stake_amount, '1- wrong user staking balance'); + assert(state.total_supply.read() == stake_amount, '1- wrong total supply'); + assert( + deploy.staking_token.balance_of(user) == amount_tokens_minted - stake_amount, + '1- wrong staking token balance' + ); + + // check 1st & 2nd event - when user stakes + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::RewardsFinished(RewardsFinished { msg: 'Rewards not active yet' })) + ); + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::Deposit(Deposit { user, amount: stake_amount })) + ); + + /// when - withdrawal + set_contract_address(user); + let withdrawal_amount = 20; + deploy.contract.withdraw(withdrawal_amount); + + /// then + // at the end the user has 80 tokens left and 20 tokens staked + let state = StakingContract::contract_state_for_testing(); + set_contract_address(deploy.contract.contract_address); + assert( + state.balance_of.read(user) == stake_amount - withdrawal_amount, + '2- wrong user staking balance' + ); + assert( + state.total_supply.read() == stake_amount - withdrawal_amount, '2- wrong total supply' + ); + assert( + deploy.staking_token.balance_of(user) == amount_tokens_minted + - stake_amount + + withdrawal_amount, + '2- wrong staking token balance' + ); + + // check 3rd & 4th events - when user withdraws + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::RewardsFinished(RewardsFinished { msg: 'Rewards not active yet' })) + ); + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::Withdrawal(Withdrawal { user, amount: withdrawal_amount })) + ); + } + + #[test] + #[available_gas(20000000)] + fn claim_rewards_3_users_scenario() { + /// set up + + // deploy + let owner = contract_address_const::<'owner'>(); + set_contract_address(owner); + let deploy = setup(); + + // mint reward tokens to the deployed contract + let reward_tokens_amount = 1000; + mint_reward_tokens_to( + deploy.contract.contract_address, + reward_tokens_amount, + deploy.reward_token.contract_address + ); + + // owner sets up rewards duration and amount + let block_timestamp: u256 = 1000; + set_block_timestamp(block_timestamp.try_into().unwrap()); + let reward_duration = 100; + // have to set again the contract_address because it got changed in mint_reward_tokens_to function + set_contract_address(owner); + deploy.contract.set_reward_duration(reward_duration); + deploy.contract.set_reward_amount(reward_tokens_amount); + + // check reward rate, last updated at and finish dates + let state = StakingContract::contract_state_for_testing(); + set_contract_address(deploy.contract.contract_address); + assert( + state.reward_rate.read() == reward_tokens_amount / reward_duration, 'Wrong reward rate' + ); + assert( + state.finish_at.read() == block_timestamp + reward_duration, 'Wrong reward finish date' + ); + assert(state.last_updated_at.read() == block_timestamp, 'Wrong reward last updated date'); + + // mint staking tokens to alice + let alice = contract_address_const::<'alice'>(); + let alice_stake_amount = 40; + let alice_amount_tokens_minted = 100; + mint_and_approve_staking_tokens_to( + alice, alice_amount_tokens_minted, deploy, alice_stake_amount + ); + + // alice stakes + set_contract_address(alice); + deploy.contract.stake(alice_stake_amount); + + // timestamp = 1000 + // r0 = 0 + // last updated at = 1000 + // last RPST (reward per staked token) for alice = r0 = 0 + // total supply = 40 + + // mint staking tokens to bob + let bob = contract_address_const::<'bob'>(); + let bob_stake_amount = 10; + let bob_amount_tokens_minted = 100; + mint_and_approve_staking_tokens_to(bob, bob_amount_tokens_minted, deploy, bob_stake_amount); + + // bob stakes + set_contract_address(bob); + set_block_timestamp(block_timestamp.try_into().unwrap() + 20); + deploy.contract.stake(bob_stake_amount); + + // timestamp = 1020 + // r1 = r0 + 10 * (1020 - 1000) / 40 = 5 + // last updated at = 1020 + // last RPST for alice = r0 = 0 + // last RPST for bob = r1 = 5 + // total supply = 50 + + // mint staking tokens to john + let john = contract_address_const::<'john'>(); + let john_stake_amount = 30; + let john_amount_tokens_minted = 100; + mint_and_approve_staking_tokens_to( + john, john_amount_tokens_minted, deploy, john_stake_amount + ); + + // john stakes + set_contract_address(john); + set_block_timestamp(block_timestamp.try_into().unwrap() + 30); + deploy.contract.stake(john_stake_amount); + + // timestamp = 1030 + // r2 = r1 + 10 * (1030 - 1020) / 50 = 7 + // last updated at = 1030 + // last RPST for alice = r0 = 0 + // last RPST for bob = r1 = 5 + // last RPST for john = r2 = 7 + // total supply = 80 + + // bob withdraws + set_contract_address(bob); + set_block_timestamp(block_timestamp.try_into().unwrap() + 50); + deploy.contract.withdraw(bob_stake_amount); + + // timestamp = 1050 + // r3 = r2 + 10 * (1050 - 1030) / 80 = 7 + 2 (< 2.5) = 9 + // last updated at = 1050 + // bob rewards = 0 + staked_tokens * (r3 - r1) = 10 * (9 - 5) = 40 + // last RPST for alice = r0 = 0 + // last RPST for bob = r3 = 9 + // last RPST for john = r2 = 7 + // total supply = 70 + + // john withdraws some of its staked tokens + set_contract_address(john); + set_block_timestamp(block_timestamp.try_into().unwrap() + 80); + deploy.contract.withdraw(john_stake_amount - 10); + + // timestamp = 1080 + // r4 = r3 + 10 * (1080 - 1050) / 70 = 9 + 4 (< 4.2857...) = 13 + // last updated at = 1080 + // bob rewards = 40 + // john rewards = 0 + staked_tokens * (r4 - r2) = 30 * (13 - 7) = 180 + // last RPST for alice = r0 = 0 + // last RPST for bob = r3 = 9 + // last RPST for john = r4 = 13 + // total supply = 50 + + // alice withdraws + set_contract_address(alice); + set_block_timestamp(block_timestamp.try_into().unwrap() + 90); + deploy.contract.withdraw(alice_stake_amount); + + // timestamp = 1090 + // r5 = r4 + 10 * (1090 - 1080) / 50 = 13 + 2 = 15 + // last updated at = 1090 + // alice rewards = 0 + staked_tokens * (r5 - r0) = 40 * (15 - 0) = 600 + // bob rewards = 40 + // john rewards = 180 + // last RPST for alice = r5 = 15 + // last RPST for bob = r3 = 9 + // last RPST for john = r4 = 13 + // total supply = 10 + + /// when + + // timestamp after the duration is finished + set_block_timestamp( + block_timestamp.try_into().unwrap() + reward_duration.try_into().unwrap() + 10 + ); + + // alice claims + deploy.contract.claim_rewards(); + + // bob claims + set_contract_address(bob); + deploy.contract.claim_rewards(); + + // john claims + set_contract_address(john); + deploy.contract.claim_rewards(); + + // timestamp = 1110 + // r6 = r5 + 10 * (1100 - 1090) / 10 = 15 + 10 = 25 + // last updated at = 1100 (becomes same as finish_at) + // alice rewards = 600 + staked_tokens * (r6 - r5) = 600 + 0 * (25 - 15) = 600 -> 0 (claimed) + // bob rewards = 40 + staked_tokens * (r6 - r3) = 40 + 0 * (25 - 9) = 40 -> 0 (claimed) + // john rewards = 180 + staked_tokens * (r6 - r4) = 180 + 10 * (25 - 13) = 300 -> 0 (claimed) + // last RPST for alice = r6 = 25 + // last RPST for bob = r6 = 25 + // last RPST for john = r6 = 25 + // total supply = 10 + + /// then + + let state = StakingContract::contract_state_for_testing(); + set_contract_address(deploy.contract.contract_address); + + // check amount of unclaimed reward tokens for each user + assert(state.unclaimed_rewards.read(alice) == 0, 'Alice: unclaimed rewards'); + assert(state.unclaimed_rewards.read(bob) == 0, 'Bob: unclaimed rewards'); + assert(state.unclaimed_rewards.read(john) == 0, 'John: unclaimed rewards'); + + // check amount of staked tokens left in contract for each user + assert(state.balance_of.read(alice) == 0, 'Alice: staked tokens left'); + assert(state.balance_of.read(bob) == 0, 'Bob: staked tokens left'); + assert(state.balance_of.read(john) == 10, 'John: wrong staked tokens'); + + // check amount of reward tokens for each user + let alice_rewards = deploy.reward_token.balance_of(alice); + let bob_rewards = deploy.reward_token.balance_of(bob); + let john_rewards = deploy.reward_token.balance_of(john); + let deployed_contract_rewards = deploy + .reward_token + .balance_of(deploy.contract.contract_address); + assert(alice_rewards == 600, 'Alice: wrong amount of rewards'); + assert(bob_rewards == 40, 'Bob: wrong amount of rewards'); + assert(john_rewards == 300, 'John: wrong amount of rewards'); + // 1000 - 600 - 40 - 300 = 60 + assert(deployed_contract_rewards == 60, 'Contract: wrong rewards'); + + // check amount of staking tokens each user has back in their balance + let alice_staking_tokens = deploy.staking_token.balance_of(alice); + let bob_staking_tokens = deploy.staking_token.balance_of(bob); + let john_staking_tokens = deploy.staking_token.balance_of(john); + assert( + alice_staking_tokens == alice_amount_tokens_minted, 'Alice: wrong amount of staking' + ); + assert(bob_staking_tokens == bob_amount_tokens_minted, 'Bob: wrong amount of staking'); + assert( + john_staking_tokens == john_amount_tokens_minted - 10, 'John: wrong amount of staking' + ); + } + + #[test] + #[available_gas(20000000)] + fn all_rewards_distributed_event() { + /// set up + + // deploy + let owner = contract_address_const::<'owner'>(); + set_contract_address(owner); + let deploy = setup(); + + // mint reward tokens to the deployed contract + let reward_tokens_amount = 1000; + mint_reward_tokens_to( + deploy.contract.contract_address, + reward_tokens_amount, + deploy.reward_token.contract_address + ); + + // owner sets up rewards duration and amount + let block_timestamp: u256 = 1000; + set_block_timestamp(block_timestamp.try_into().unwrap()); + let reward_duration = 100; + // have to set again the contract_address because it got changed in mint_reward_tokens_to function + set_contract_address(owner); + deploy.contract.set_reward_duration(reward_duration); + deploy.contract.set_reward_amount(reward_tokens_amount); + + // mint staking tokens to alice + let alice = contract_address_const::<'alice'>(); + let alice_stake_amount = 100; + mint_and_approve_staking_tokens_to(alice, alice_stake_amount, deploy, alice_stake_amount); + + // alice stakes + set_contract_address(alice); + deploy.contract.stake(alice_stake_amount); + + // alice claims her rewards after the duration is over + set_block_timestamp( + block_timestamp.try_into().unwrap() + reward_duration.try_into().unwrap() + ); + deploy.contract.claim_rewards(); + + /// when + + // mint staking tokens to bob + let bob = contract_address_const::<'bob'>(); + let bob_stake_amount = 50; + mint_and_approve_staking_tokens_to(bob, bob_stake_amount, deploy, bob_stake_amount); + + // bob stakes + set_contract_address(bob); + deploy.contract.stake(bob_stake_amount); + + /// then + + // check 1st event - when alice stakes + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::Deposit(Deposit { user: alice, amount: alice_stake_amount })) + ); + // check 2nd event - when alice claims + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::RewardsFinished(RewardsFinished { msg: 'Rewards all distributed' })) + ); + // check 3rd & 4th events - when bob stakes + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::RewardsFinished(RewardsFinished { msg: 'Rewards all distributed' })) + ); + assert_eq!( + pop_log(deploy.contract.contract_address), + Option::Some(Event::Deposit(Deposit { user: bob, amount: bob_stake_amount })) + ); + } + + #[test] + #[available_gas(20000000)] + fn set_up_reward_complex() { + /// Set up + + // deploy + let owner = contract_address_const::<'owner'>(); + set_contract_address(owner); + let deploy = setup(); + + // mint reward tokens to the deployed contract + let reward_tokens_amount = 1000; + mint_reward_tokens_to( + deploy.contract.contract_address, + reward_tokens_amount, + deploy.reward_token.contract_address + ); + + // owner sets up rewards duration and amount + let block_timestamp: u256 = 1000; + set_block_timestamp(block_timestamp.try_into().unwrap()); + let reward_duration = 100; + let initial_reward = 400; + // have to set again the contract_address because it got changed in mint_reward_tokens_to function + set_contract_address(owner); + deploy.contract.set_reward_duration(reward_duration); + deploy.contract.set_reward_amount(initial_reward); + + // middle check + let state = StakingContract::contract_state_for_testing(); + set_contract_address(deploy.contract.contract_address); + + // timestamp = 1000 + // finish_at = 1100 + // reward_rate = 400 / 100 = 4 tokens/timestamp_unit + + assert(state.finish_at.read() == block_timestamp + reward_duration, '1- Wrong finish date'); + assert(state.last_updated_at.read() == block_timestamp, '1- Wrong last update date'); + assert(state.reward_rate.read() == 4, '1- Wrong reward rate'); + + /// When + + // in the middle of the duration, the owner adds some rewards + let middle_timestamp = block_timestamp + 50; + set_block_timestamp(middle_timestamp.try_into().unwrap()); + let rewards_to_add = 300; + set_contract_address(owner); + deploy.contract.set_reward_amount(rewards_to_add); + + /// Then + + // timestamp = 1050 + // old_finish_at = 1100 + // old_reward_rate = 4 + // remaining_rewards = 4 * (1100 - 1050) = 200 tokens + // new_reward_rate = (200 + 300) / 100 = 5 + // new_finish_at = 1050 + 100 = 1150 + + let state = StakingContract::contract_state_for_testing(); + set_contract_address(deploy.contract.contract_address); + + // the finish_at date is reset + assert( + state.finish_at.read() == middle_timestamp + reward_duration, '2- Wrong finish date' + ); + assert(state.last_updated_at.read() == middle_timestamp, '2- Wrong last update date'); + assert(state.reward_rate.read() == 5, ''); + } +} diff --git a/listings/applications/staking/src/tests/tokens.cairo b/listings/applications/staking/src/tests/tokens.cairo new file mode 100644 index 00000000..c6ce3a19 --- /dev/null +++ b/listings/applications/staking/src/tests/tokens.cairo @@ -0,0 +1,59 @@ +#[starknet::contract] +pub mod RewardToken { + use openzeppelin::token::erc20::ERC20Component; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) { + self.erc20.initializer(name, symbol); + } +} + +#[starknet::contract] +pub mod StakingToken { + use openzeppelin::token::erc20::ERC20Component; + use starknet::ContractAddress; + + component!(path: ERC20Component, storage: erc20, event: ERC20Event); + + #[abi(embed_v0)] + impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20InternalImpl = ERC20Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + erc20: ERC20Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + ERC20Event: ERC20Component::Event, + } + + #[constructor] + fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) { + self.erc20.initializer(name, symbol); + } +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a02b5802..4f948385 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -55,6 +55,7 @@ Summary - [Defi Vault](./ch01/simple_vault.md) - [ERC20 Token](./ch01/erc20.md) - [Constant Product AMM](./ch01/constant-product-amm.md) +- [Staking](./ch01/staking.md) diff --git a/src/ch01/staking.md b/src/ch01/staking.md new file mode 100644 index 00000000..dab8d3ab --- /dev/null +++ b/src/ch01/staking.md @@ -0,0 +1,31 @@ +# Staking contract + +The following staking contract is designed to allow users to stake tokens in exchange for reward tokens over a specified duration. Here's a quick summary of how it operates and what functionalities it supports: + +### Key Features: + +1. Token staking and unstaking: + - Users can stake a ERC20 token, specified at deployment. + - Users can withdraw their staked tokens at any time. + +2. Reward calculation and distribution: + - The rewards are distributed as an ERC20, also specified at deployment (can be different from the staking token). + - Rewards are calculated based on the duration of staking and the amount the user staked relative to the total staked amount by all users. + - A user’s reward accumulates over time up until the reward period's end and can be claimed anytime by the user. + +3. Dynamic reward rates: + - The reward rate is determined by the total amount of reward tokens over a set period (duration). + - The reward rate can be adjusted during the rewards period if new rewards are added before the current reward period finishes. + - Even after a reward period finishes, a new reward duration and new rewards can be setup if desired. + +4. Ownership and administration: + - Only the owner of the contract can set the rewards amount and duration. + +> The reward mechanism ensures that rewards are distributed fairly based on the amount and duration of tokens staked by each user. + +The following implementation is the Cairo adaptation of the [Solidity by example Staking Rewards contract](https://solidity-by-example.org/defi/staking-rewards/), with a little adaptation allowing to keep track of the amount of total distributed reward tokens in order to emit an event when the remaining reward tokens amount falls down to 0. + +```rust +{{#include ../../listings/applications/staking/src/contract.cairo}} +``` + From 78f3b6360f31be8974a0db1a895278899caa6aea Mon Sep 17 00:00:00 2001 From: hudem1 <55464342+hudem1@users.noreply.github.com> Date: Tue, 4 Jun 2024 23:15:45 +0900 Subject: [PATCH 11/36] feat(enums): Enums in contract (#212) * feat(enums): Enums in contract * fix: small fmt --------- Co-authored-by: julio4 --- .../cairo_cheatsheet/src/enum_example.cairo | 83 +++++++++++++++++++ .../cairo_cheatsheet/src/lib.cairo | 1 + src/SUMMARY.md | 1 + src/ch00/cairo_cheatsheet/enums.md | 27 ++++++ 4 files changed, 112 insertions(+) create mode 100644 listings/getting-started/cairo_cheatsheet/src/enum_example.cairo create mode 100644 src/ch00/cairo_cheatsheet/enums.md diff --git a/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo b/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo new file mode 100644 index 00000000..5e632ee6 --- /dev/null +++ b/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo @@ -0,0 +1,83 @@ +// ANCHOR: enums +#[derive(Drop, Serde, Copy, starknet::Store)] +struct Position { + x: u32, + y: u32, +} + +#[derive(Drop, Serde, Copy, starknet::Store)] +enum UserCommand { + Login, + UpdateProfile, + Logout, +} + +#[derive(Drop, Serde, Copy, starknet::Store)] +enum Action { + Quit, + Move: Position, + SendMessage: felt252, + ChangeAvatarColor: (u8, u8, u8), + ProfileState: UserCommand +} +// ANCHOR_END: enums + +// ANCHOR: enum_contract +#[starknet::interface] +trait IEnumContract { + fn register_action(ref self: TContractState, action: Action); + fn generate_default_actions_list(self: @TContractState) -> Array; +} + +#[starknet::contract] +mod EnumContract { + use core::clone::Clone; + use core::traits::Into; + use super::IEnumContract; + use super::{Action, Position, UserCommand}; + + #[storage] + struct Storage { + most_recent_action: Action, + } + + #[abi(embed_v0)] + impl IEnumContractImpl of IEnumContract { + fn register_action(ref self: ContractState, action: Action) { + // quick note: match takes ownership of variable (but enum Action implements Copy trait) + match action { + Action::Quit => { println!("Quit"); }, + Action::Move(value) => { println!("Move with x: {} and y: {}", value.x, value.y); }, + Action::SendMessage(msg) => { println!("Write with message: {}", msg); }, + Action::ChangeAvatarColor(( + r, g, b + )) => { println!("Change color to r: {}, g: {}, b: {}", r, g, b); }, + Action::ProfileState(state) => { + let profile_state = match state { + UserCommand::Login => 1, + UserCommand::UpdateProfile => 2, + UserCommand::Logout => 3, + }; + println!("profile_state: {}", profile_state); + } + }; + + self.most_recent_action.write(action); + } + + fn generate_default_actions_list(self: @ContractState) -> Array { + let actions = array![ + Action::Quit, + Action::Move(Position { x: 1, y: 2 }), + Action::SendMessage('here is my message'), + Action::ChangeAvatarColor((1, 2, 3)), + Action::ProfileState(UserCommand::Login), + ]; + + actions + } + } +} +// ANCHOR_END: enum_contract + + diff --git a/listings/getting-started/cairo_cheatsheet/src/lib.cairo b/listings/getting-started/cairo_cheatsheet/src/lib.cairo index 63b38485..b495fd77 100644 --- a/listings/getting-started/cairo_cheatsheet/src/lib.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/lib.cairo @@ -3,6 +3,7 @@ mod mapping_example; mod felt_example; mod loop_example; mod while_example; +mod enum_example; mod match_example; mod struct_example; mod type_casting_example; diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 4f948385..f76fdac3 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -35,6 +35,7 @@ Summary - [while](./ch00/cairo_cheatsheet/while.md) - [if let](./ch00/cairo_cheatsheet/if_let.md) - [while let](./ch00/cairo_cheatsheet/while_let.md) + - [Enums](./ch00/cairo_cheatsheet/enums.md) - [Match](./ch00/cairo_cheatsheet/match.md) - [Tuples](./ch00/cairo_cheatsheet/tuples.md) - [Struct](./ch00/cairo_cheatsheet/struct.md) diff --git a/src/ch00/cairo_cheatsheet/enums.md b/src/ch00/cairo_cheatsheet/enums.md new file mode 100644 index 00000000..ba5694fe --- /dev/null +++ b/src/ch00/cairo_cheatsheet/enums.md @@ -0,0 +1,27 @@ +# Enums + +Just like other programming languages, enums (enumerations) are used in cairo to define variables that can only hold a set of predefined variants (= enum options), enhancing code readability and safety. They facilitate strong type checking and are ideal for organizing related options and supporting structured logic through pattern matching for example, which is also described in the next chapter. + +In cairo, `enum variants` can hold different data types (the unit type, structs, other enums, tuples, default core library types, arrays, dictionaries, ...), as shown in the code snippet below. Furthermore, as a quick reminder, enums are expressions, meaning they can return values. + +```rust +{{#include ../../../listings/getting-started/cairo_cheatsheet/src/enum_example.cairo:enums}} +``` + +Enums can be declared both inside and outside a contract. If declared outside, they need to be imported inside using the `use` keyword, just like other imports. + +1. Storing enums in contract + + - It is possible to store `enums` in the contract storage. But unlike most of the core library types which implement the `Store` trait, enums are custom types and therefore do not automatically implement the `Store` trait. The enum as well as all of its variants have to explicitly implement the `Store` trait in order for it to be stored inside a contract storage. + + - If all of its variants implement the `Store` trait, implementing the `Store` trait on the enum is as simple as deriving it, using `#[derive(starknet::Store)]` (as shown in example above). If not, the `Store` trait has to be manually implemented -- see an example of manually implementing the `Store` trait for a complex type in chapter [Storing Arrays](https://starknet-by-example.voyager.online/ch02/storing_arrays.html). + +2. Enums as parameters and return values to entrypoints + + - It is possible to pass `enums` to contract entrypoints as parameters, as well as return them from entrypoints. For that purpose, the enum needs to be serializable and dropable, hence the derivation of traits `Serde` and `Drop` in the above code snippet. + +Here is an example of a contract illustrating the above statements : + +```rust +{{#include ../../../listings/getting-started/cairo_cheatsheet/src/enum_example.cairo:enum_contract}} +``` From 3b9d93e2d24d10a0a92ada1de4a56f40c5fe023c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Misi=C4=87?= Date: Wed, 5 Jun 2024 08:14:17 +0200 Subject: [PATCH 12/36] Application chapter: grammar, wording, typos, `!addr.is_zero` -> `addr.is_non_zero` (#213) * upgradeable > use is_non_zero * simple_vault > grammar * not is_zero -> is_non_zero * update sbe links * staking > wording * amm > fix brackets + grammar --------- Co-authored-by: Nenad --- .../constant_product_amm/src/contracts.cairo | 6 ++-- listings/applications/erc20/src/token.cairo | 8 +++--- .../applications/staking/src/contract.cairo | 4 +-- .../src/upgradeable_contract_v0.cairo | 2 +- .../src/upgradeable_contract_v1.cairo | 2 +- po/es.po | 28 +++++++++---------- po/zh-cn.po | 24 ++++++++-------- src/ch01/constant-product-amm.md | 2 +- src/ch01/erc20.md | 4 +-- src/ch01/simple_vault.md | 4 +-- src/ch01/staking.md | 24 ++++++++-------- 11 files changed, 55 insertions(+), 53 deletions(-) diff --git a/listings/applications/constant_product_amm/src/contracts.cairo b/listings/applications/constant_product_amm/src/contracts.cairo index 9c93d88c..0b1bd679 100644 --- a/listings/applications/constant_product_amm/src/contracts.cairo +++ b/listings/applications/constant_product_amm/src/contracts.cairo @@ -147,7 +147,7 @@ pub mod ConstantProductAmm { assert(reserve0 * amount1 == reserve1 * amount0, 'x / y != dx / dy'); } - // How much shares to mint? + // How many shares to mint? // // f(x, y) = value of liquidity // We will define f(x, y) = sqrt(xy) @@ -178,11 +178,11 @@ pub mod ConstantProductAmm { // // Multiply by sqrt(x) / sqrt(x) // Equation 2 = (sqrt(x^2y + 2xydx + dx^2 * y) - sqrt(x^2y)) / sqrt(x^2y) - // = (sqrt(y)(sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(y)sqrt(x^2)) + // = (sqrt(y)(sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2))) / (sqrt(y)sqrt(x^2)) // sqrt(y) on top and bottom cancels out // // --- Equation 3 --- - // Equation 2 = (sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / (sqrt(x^2) + // Equation 2 = (sqrt(x^2 + 2xdx + dx^2) - sqrt(x^2)) / sqrt(x^2) // = (sqrt((x + dx)^2) - sqrt(x^2)) / sqrt(x^2) // = ((x + dx) - x) / x // = dx / x diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index 51660642..c2477789 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -165,8 +165,8 @@ pub mod erc20 { recipient: ContractAddress, amount: felt252 ) { - assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO); - assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO); + assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO); + assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO); self.balances.write(sender, self.balances.read(sender) - amount); self.balances.write(recipient, self.balances.read(recipient) + amount); self.emit(Transfer { from: sender, to: recipient, value: amount }); @@ -188,13 +188,13 @@ pub mod erc20 { spender: ContractAddress, amount: felt252 ) { - assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO); + assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO); self.allowances.write((owner, spender), amount); self.emit(Approval { owner, spender, value: amount }); } fn mint(ref self: ContractState, recipient: ContractAddress, amount: felt252) { - assert(!recipient.is_zero(), Errors::MINT_TO_ZERO); + assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO); let supply = self.total_supply.read() + amount; self.total_supply.write(supply); let balance = self.balances.read(recipient) + amount; diff --git a/listings/applications/staking/src/contract.cairo b/listings/applications/staking/src/contract.cairo index e158c8eb..ed17e14d 100644 --- a/listings/applications/staking/src/contract.cairo +++ b/listings/applications/staking/src/contract.cairo @@ -126,7 +126,7 @@ pub mod StakingContract { self.reward_rate.write(rate); - // even if the previous reward duration was not finished, we reset the finish_at variable + // even if the previous reward duration has not finished, we reset the finish_at variable self.finish_at.write(block_timestamp + self.duration.read()); self.last_updated_at.write(block_timestamp); @@ -216,7 +216,7 @@ pub mod StakingContract { } fn send_rewards_finished_event(ref self: ContractState) { - // check if we send a RewardsFinished event + // check whether we should send a RewardsFinished event if self.last_updated_at.read() == self.finish_at.read() { let total_rewards = self.reward_rate.read() * self.duration.read(); diff --git a/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo b/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo index e2071b92..c2aac9ca 100644 --- a/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo +++ b/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo @@ -32,7 +32,7 @@ pub mod UpgradeableContract_V0 { impl UpgradeableContract of super::IUpgradeableContract { // ANCHOR: upgrade fn upgrade(ref self: ContractState, impl_hash: ClassHash) { - assert(!impl_hash.is_zero(), 'Class hash cannot be zero'); + assert(impl_hash.is_non_zero(), 'Class hash cannot be zero'); starknet::syscalls::replace_class_syscall(impl_hash).unwrap_syscall(); self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })) } diff --git a/listings/applications/upgradeable_contract/src/upgradeable_contract_v1.cairo b/listings/applications/upgradeable_contract/src/upgradeable_contract_v1.cairo index b8402f85..78baa8b1 100644 --- a/listings/applications/upgradeable_contract/src/upgradeable_contract_v1.cairo +++ b/listings/applications/upgradeable_contract/src/upgradeable_contract_v1.cairo @@ -29,7 +29,7 @@ pub mod UpgradeableContract_V1 { #[abi(embed_v0)] impl UpgradeableContract of super::IUpgradeableContract { fn upgrade(ref self: ContractState, impl_hash: ClassHash) { - assert(!impl_hash.is_zero(), 'Class hash cannot be zero'); + assert(impl_hash.is_non_zero(), 'Class hash cannot be zero'); starknet::syscalls::replace_class_syscall(impl_hash).unwrap_syscall(); self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })) } diff --git a/po/es.po b/po/es.po index 2108a25c..8443d1e9 100644 --- a/po/es.po +++ b/po/es.po @@ -2746,7 +2746,7 @@ msgid "" "# impl UpgradeableContract of super::IUpgradeableContract " "{\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -2794,7 +2794,7 @@ msgstr "" "# impl UpgradeableContract of super::IUpgradeableContract " "{\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -7780,7 +7780,7 @@ msgid "" " impl UpgradeableContract of super::IUpgradeableContract " "{\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -7826,7 +7826,7 @@ msgstr "" " impl UpgradeableContract of super::IUpgradeableContract " "{\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -7890,7 +7890,7 @@ msgid "" " impl UpgradeableContract of super::IUpgradeableContract " "{\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -7936,7 +7936,7 @@ msgstr "" " impl UpgradeableContract of super::IUpgradeableContract " "{\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -8481,8 +8481,8 @@ msgid "" " recipient: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);\n" -" assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);\n" +" assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO);\n" " self.balances.write(sender, self.balances.read(sender) - " "amount);\n" " self.balances.write(recipient, self.balances.read(recipient) + " @@ -8507,14 +8507,14 @@ msgid "" " spender: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);\n" +" assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO);\n" " self.allowances.write((owner, spender), amount);\n" " self.emit(Approval { owner, spender, value: amount });\n" " }\n" "\n" " fn mint(ref self: ContractState, recipient: ContractAddress, amount: " "felt252) {\n" -" assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO);\n" " let supply = self.total_supply.read() + amount; // What can go " "wrong here?\n" " self.total_supply.write(supply);\n" @@ -8682,8 +8682,8 @@ msgstr "" " recipient: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);\n" -" assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);\n" +" assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO);\n" " self.balances.write(sender, self.balances.read(sender) - " "amount);\n" " self.balances.write(recipient, self.balances.read(recipient) + " @@ -8708,14 +8708,14 @@ msgstr "" " spender: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);\n" +" assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO);\n" " self.allowances.write((owner, spender), amount);\n" " self.emit(Approval { owner, spender, value: amount });\n" " }\n" "\n" " fn mint(ref self: ContractState, recipient: ContractAddress, amount: " "felt252) {\n" -" assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO);\n" " let supply = self.total_supply.read() + amount; // What can go " "wrong here?\n" " self.total_supply.write(supply);\n" diff --git a/po/zh-cn.po b/po/zh-cn.po index a1b8bb7a..90953985 100644 --- a/po/zh-cn.po +++ b/po/zh-cn.po @@ -4269,7 +4269,7 @@ msgid "" " #[abi(embed_v0)]\n" " impl UpgradeableContract of super::IUpgradeableContract {\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -4314,7 +4314,7 @@ msgstr "" " #[abi(embed_v0)]\n" " impl UpgradeableContract of super::IUpgradeableContract {\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -4377,7 +4377,7 @@ msgid "" " #[abi(embed_v0)]\n" " impl UpgradeableContract of super::IUpgradeableContract {\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -4422,7 +4422,7 @@ msgstr "" " #[abi(embed_v0)]\n" " impl UpgradeableContract of super::IUpgradeableContract {\n" " fn upgrade(ref self: ContractState, impl_hash: ClassHash) {\n" -" assert(!impl_hash.is_zero(), 'Class hash cannot be zero');\n" +" assert(impl_hash.is_non_zero(), 'Class hash cannot be zero');\n" " starknet::replace_class_syscall(impl_hash).unwrap_syscall();\n" " self.emit(Event::Upgraded(Upgraded { implementation: " "impl_hash }))\n" @@ -4961,8 +4961,8 @@ msgid "" " recipient: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);\n" -" assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);\n" +" assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO);\n" " self.balances.write(sender, self.balances.read(sender) - " "amount);\n" " self.balances.write(recipient, self.balances.read(recipient) + " @@ -4987,14 +4987,14 @@ msgid "" " spender: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);\n" +" assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO);\n" " self.allowances.write((owner, spender), amount);\n" " self.emit(Approval { owner, spender, value: amount });\n" " }\n" "\n" " fn mint(ref self: ContractState, recipient: ContractAddress, amount: " "felt252) {\n" -" assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO);\n" " let supply = self.total_supply.read() + amount; // What can go " "wrong here?\n" " self.total_supply.write(supply);\n" @@ -5162,8 +5162,8 @@ msgstr "" " recipient: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!sender.is_zero(), Errors::TRANSFER_FROM_ZERO);\n" -" assert(!recipient.is_zero(), Errors::TRANSFER_TO_ZERO);\n" +" assert(sender.is_non_zero(), Errors::TRANSFER_FROM_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::TRANSFER_TO_ZERO);\n" " self.balances.write(sender, self.balances.read(sender) - " "amount);\n" " self.balances.write(recipient, self.balances.read(recipient) + " @@ -5188,14 +5188,14 @@ msgstr "" " spender: ContractAddress,\n" " amount: felt252\n" " ) {\n" -" assert(!spender.is_zero(), Errors::APPROVE_TO_ZERO);\n" +" assert(spender.is_non_zero(), Errors::APPROVE_TO_ZERO);\n" " self.allowances.write((owner, spender), amount);\n" " self.emit(Approval { owner, spender, value: amount });\n" " }\n" "\n" " fn mint(ref self: ContractState, recipient: ContractAddress, amount: " "felt252) {\n" -" assert(!recipient.is_zero(), Errors::MINT_TO_ZERO);\n" +" assert(recipient.is_non_zero(), Errors::MINT_TO_ZERO);\n" " let supply = self.total_supply.read() + amount; // What can go " "wrong here?\n" " self.total_supply.write(supply);\n" diff --git a/src/ch01/constant-product-amm.md b/src/ch01/constant-product-amm.md index 4b80861e..2ec959da 100644 --- a/src/ch01/constant-product-amm.md +++ b/src/ch01/constant-product-amm.md @@ -1,6 +1,6 @@ # Constant Product AMM -This is the Cairo adaptation of the [Solidity by example Constant Product AMM](https://solidity-by-example.org/defi/constant-product-amm/). +This is the Cairo adaptation of the [Solidity by Example - Constant Product AMM](https://solidity-by-example.org/defi/constant-product-amm/). ```rust {{#include ../../listings/applications/constant_product_amm/src/contracts.cairo:ConstantProductAmmContract}} diff --git a/src/ch01/erc20.md b/src/ch01/erc20.md index 3eaec60c..af0085ac 100644 --- a/src/ch01/erc20.md +++ b/src/ch01/erc20.md @@ -2,13 +2,13 @@ Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20) are called ERC20 tokens. They are used to represent fungible assets. -To create an ERC20 conctract, it must implement the following interface: +To create an ERC20 contract, it must implement the following interface: ```rust {{#include ../../listings/applications/erc20/src/token.cairo:interface}} ``` -In Starknet, function names should be written in *snake_case*. This is not the case in Solidity, where function names are written in *camelCase*. +In Starknet, function names should be written in _snake_case_. This is not the case in Solidity, where function names are written in _camelCase_. The Starknet ERC20 interface is therefore slightly different from the Solidity ERC20 interface. Here's an implementation of the ERC20 interface in Cairo: diff --git a/src/ch01/simple_vault.md b/src/ch01/simple_vault.md index ab465fc5..749686c5 100644 --- a/src/ch01/simple_vault.md +++ b/src/ch01/simple_vault.md @@ -1,11 +1,11 @@ # Simple Defi Vault -This is the Cairo adaptation of the [Solidity by example Vault](https://solidity-by-example.org/defi/vault/). +This is the Cairo adaptation of the [Solidity by Example - Vault](https://solidity-by-example.org/defi/vault/). Here's how it works: - When a user deposits a token, the contract calculates the amount of shares to mint. -- When a user withdraws, the contract burns their shares, calculates the yield, and withdraw both the yield and the initial amount of token deposited. +- When a user withdraws, the contract burns their shares, calculates the yield, and withdraws both the yield and the initial amount of tokens deposited. ```rust {{#include ../../listings/applications/simple_vault/src/simple_vault.cairo}} diff --git a/src/ch01/staking.md b/src/ch01/staking.md index dab8d3ab..2a0eb83d 100644 --- a/src/ch01/staking.md +++ b/src/ch01/staking.md @@ -5,27 +5,29 @@ The following staking contract is designed to allow users to stake tokens in exc ### Key Features: 1. Token staking and unstaking: - - Users can stake a ERC20 token, specified at deployment. - - Users can withdraw their staked tokens at any time. + + - Users can stake an ERC20 token, specified at deployment. + - Users can withdraw their staked tokens at any time. 2. Reward calculation and distribution: - - The rewards are distributed as an ERC20, also specified at deployment (can be different from the staking token). - - Rewards are calculated based on the duration of staking and the amount the user staked relative to the total staked amount by all users. - - A user’s reward accumulates over time up until the reward period's end and can be claimed anytime by the user. + + - The rewards are distributed as an ERC20, also specified at deployment (can be different from the staking token). + - Rewards are calculated based on the duration of staking and the amount the user staked relative to the total staked amount by all users. + - A user’s reward accumulates over time up until the reward period's end and can be claimed anytime by the user. 3. Dynamic reward rates: - - The reward rate is determined by the total amount of reward tokens over a set period (duration). - - The reward rate can be adjusted during the rewards period if new rewards are added before the current reward period finishes. - - Even after a reward period finishes, a new reward duration and new rewards can be setup if desired. + + - The reward rate is determined by the total amount of reward tokens over a set period (duration). + - The reward rate can be adjusted during the rewards period if new rewards are added before the current reward period finishes. + - Even after a reward period finishes, a new reward duration and new rewards can be set up if desired. 4. Ownership and administration: - - Only the owner of the contract can set the rewards amount and duration. + - Only the owner of the contract can set the rewards amount and duration. > The reward mechanism ensures that rewards are distributed fairly based on the amount and duration of tokens staked by each user. -The following implementation is the Cairo adaptation of the [Solidity by example Staking Rewards contract](https://solidity-by-example.org/defi/staking-rewards/), with a little adaptation allowing to keep track of the amount of total distributed reward tokens in order to emit an event when the remaining reward tokens amount falls down to 0. +The following implementation is the Cairo adaptation of the [Solidity by Example - Staking Rewards contract](https://solidity-by-example.org/defi/staking-rewards/). It includes a small adaptation to keep track of the amount of total distributed reward tokens and emit an event when the remaining reward token amount reaches 0. ```rust {{#include ../../listings/applications/staking/src/contract.cairo}} ``` - From e287afa2cb33cf5aa5dfdb7f36b6fe1c23efe556 Mon Sep 17 00:00:00 2001 From: saimeunt Date: Wed, 5 Jun 2024 08:16:26 +0200 Subject: [PATCH 13/36] add time locked transactions example (#201) * add time locked transactions example * install snforge in gh-action * fix: refactor timelock example --- .github/workflows/verify_cairo_programs.yml | 6 +- .tool-versions | 3 +- Scarb.lock | 14 ++ Scarb.toml | 3 +- .../applications/components/src/ownable.cairo | 4 +- listings/applications/timelock/.gitignore | 1 + listings/applications/timelock/Scarb.toml | 19 ++ .../applications/timelock/src/erc721.cairo | 44 ++++ listings/applications/timelock/src/lib.cairo | 8 + .../timelock/src/tests/timelock.cairo | 228 ++++++++++++++++++ .../timelock/src/tests/utils.cairo | 68 ++++++ .../applications/timelock/src/timelock.cairo | 155 ++++++++++++ src/SUMMARY.md | 1 + src/ch01/timelock.md | 7 + 14 files changed, 556 insertions(+), 5 deletions(-) create mode 100644 listings/applications/timelock/.gitignore create mode 100644 listings/applications/timelock/Scarb.toml create mode 100644 listings/applications/timelock/src/erc721.cairo create mode 100644 listings/applications/timelock/src/lib.cairo create mode 100644 listings/applications/timelock/src/tests/timelock.cairo create mode 100644 listings/applications/timelock/src/tests/utils.cairo create mode 100644 listings/applications/timelock/src/timelock.cairo create mode 100644 src/ch01/timelock.md diff --git a/.github/workflows/verify_cairo_programs.yml b/.github/workflows/verify_cairo_programs.yml index 47d17c6a..81a3a802 100644 --- a/.github/workflows/verify_cairo_programs.yml +++ b/.github/workflows/verify_cairo_programs.yml @@ -4,6 +4,7 @@ on: pull_request: branches: - main + workflow_dispatch: jobs: compile_and_verify: @@ -14,7 +15,7 @@ jobs: uses: actions/checkout@v4 with: fetch-depth: 0 - + - name: Configure upstream repository run: | git remote add upstream https://github.com/NethermindEth/StarknetByExample @@ -23,6 +24,9 @@ jobs: - name: Install scarb uses: software-mansion/setup-scarb@v1 + - name: Install snforge + uses: foundry-rs/setup-snfoundry@v3 + - name: Run build script run: | chmod +x scripts/cairo_programs_verifier.sh diff --git a/.tool-versions b/.tool-versions index 441b479d..c62bafce 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1 +1,2 @@ -scarb 2.6.4 \ No newline at end of file +scarb 2.6.4 +starknet-foundry 0.24.0 diff --git a/Scarb.lock b/Scarb.lock index 81ae93eb..31034f8c 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -101,6 +101,11 @@ version = "0.1.0" name = "simple_vault" version = "0.1.0" +[[package]] +name = "snforge_std" +version = "0.24.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.24.0#95e9fb09cb91b3c05295915179ee1b55bf923653" + [[package]] name = "staking" version = "0.1.0" @@ -132,6 +137,15 @@ version = "0.1.0" name = "testing_how_to" version = "0.1.0" +[[package]] +name = "timelock" +version = "0.1.0" +dependencies = [ + "components", + "openzeppelin", + "snforge_std", +] + [[package]] name = "upgradeable_contract" version = "0.1.0" diff --git a/Scarb.toml b/Scarb.toml index e5c678ae..4a232645 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -13,10 +13,11 @@ test = "$(git rev-parse --show-toplevel)/scripts/test_resolver.sh" [workspace.dependencies] starknet = ">=2.6.3" -# snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.11.0" } openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.11.0" } +components = { path = "listings/applications/components" } # [workspace.dev-dependencies] +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.24.0" } # openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.11.0" } [workspace.package] diff --git a/listings/applications/components/src/ownable.cairo b/listings/applications/components/src/ownable.cairo index 85a70aef..c73180e3 100644 --- a/listings/applications/components/src/ownable.cairo +++ b/listings/applications/components/src/ownable.cairo @@ -7,7 +7,7 @@ pub trait IOwnable { fn renounce_ownership(ref self: TContractState); } -mod Errors { +pub mod Errors { pub const UNAUTHORIZED: felt252 = 'Not owner'; pub const ZERO_ADDRESS_OWNER: felt252 = 'Owner cannot be zero'; pub const ZERO_ADDRESS_CALLER: felt252 = 'Caller cannot be zero'; @@ -43,7 +43,7 @@ pub mod ownable_component { } #[embeddable_as(Ownable)] - impl OwnableImpl< + pub impl OwnableImpl< TContractState, +HasComponent > of super::IOwnable> { fn owner(self: @ComponentState) -> ContractAddress { diff --git a/listings/applications/timelock/.gitignore b/listings/applications/timelock/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/applications/timelock/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/applications/timelock/Scarb.toml b/listings/applications/timelock/Scarb.toml new file mode 100644 index 00000000..a3eaafbf --- /dev/null +++ b/listings/applications/timelock/Scarb.toml @@ -0,0 +1,19 @@ +[package] +name = "timelock" +version.workspace = true +edition = "2023_11" + +[dependencies] +starknet.workspace = true +# Starknet Foundry: +snforge_std.workspace = true +# OpenZeppelin: +openzeppelin.workspace = true +# StarknetByExample Components +components.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +casm = true diff --git a/listings/applications/timelock/src/erc721.cairo b/listings/applications/timelock/src/erc721.cairo new file mode 100644 index 00000000..be7a023c --- /dev/null +++ b/listings/applications/timelock/src/erc721.cairo @@ -0,0 +1,44 @@ +#[starknet::contract] +pub mod ERC721 { + use starknet::ContractAddress; + use openzeppelin::introspection::src5::SRC5Component; + use openzeppelin::token::erc721::ERC721Component; + + component!(path: SRC5Component, storage: src5, event: SRC5Event); + component!(path: ERC721Component, storage: erc721, event: ERC721Event); + + // ERC20Mixin + #[abi(embed_v0)] + impl ERC721MixinImpl = ERC721Component::ERC721MixinImpl; + impl ERC721InternalImpl = ERC721Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + #[substorage(v0)] + erc721: ERC721Component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event, + #[flat] + ERC721Event: ERC721Component::Event, + } + + #[constructor] + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + base_uri: ByteArray, + recipient: ContractAddress, + token_id: u256 + ) { + self.erc721.initializer(name, symbol, base_uri); + self.erc721._mint(recipient, token_id); + } +} diff --git a/listings/applications/timelock/src/lib.cairo b/listings/applications/timelock/src/lib.cairo new file mode 100644 index 00000000..8c08585f --- /dev/null +++ b/listings/applications/timelock/src/lib.cairo @@ -0,0 +1,8 @@ +#[cfg(test)] +mod tests { + #[feature("safe_dispatcher")] + mod timelock; + mod utils; +} +mod erc721; +mod timelock; diff --git a/listings/applications/timelock/src/tests/timelock.cairo b/listings/applications/timelock/src/tests/timelock.cairo new file mode 100644 index 00000000..f3e6c871 --- /dev/null +++ b/listings/applications/timelock/src/tests/timelock.cairo @@ -0,0 +1,228 @@ +use core::panic_with_felt252; +use starknet::get_block_timestamp; +use starknet::account::Call; +use core::poseidon::{PoseidonTrait, poseidon_hash_span}; +use core::hash::{HashStateTrait, HashStateExTrait}; +use snforge_std::{ + cheat_caller_address, cheat_block_timestamp, CheatSpan, spy_events, SpyOn, EventSpy, + EventAssertions +}; +use openzeppelin::token::erc721::interface::IERC721DispatcherTrait; +use openzeppelin::token::erc721::erc721::ERC721Component; +use components::ownable; +use timelock::timelock::{TimeLock, ITimeLockDispatcherTrait, ITimeLockSafeDispatcherTrait}; +use timelock::tests::utils::{TimeLockTestTrait, TOKEN_ID, OTHER}; + +#[test] +fn test_get_tx_id_success() { + let timelock_test = TimeLockTestTrait::setup(); + let timestamp = timelock_test.get_timestamp(); + let tx_id = timelock_test.timelock.get_tx_id(timelock_test.get_call(), timestamp); + let Call { to, selector, calldata } = timelock_test.get_call(); + let hash = PoseidonTrait::new() + .update(to.into()) + .update(selector.into()) + .update(poseidon_hash_span(calldata)) + .update(timestamp.into()) + .finalize(); + assert_eq!(tx_id, hash); +} + +#[test] +fn test_queue_only_owner() { + let timelock_test = TimeLockTestTrait::setup(); + cheat_caller_address(timelock_test.timelock_address, OTHER(), CheatSpan::TargetCalls(1)); + match timelock_test + .timelock_safe + .queue(timelock_test.get_call(), timelock_test.get_timestamp()) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { assert_eq!(*panic_data.at(0), ownable::Errors::UNAUTHORIZED); } + } +} + +#[test] +fn test_queue_already_queued() { + let timelock_test = TimeLockTestTrait::setup(); + let timestamp = timelock_test.get_timestamp(); + timelock_test.timelock.queue(timelock_test.get_call(), timestamp); + match timelock_test.timelock_safe.queue(timelock_test.get_call(), timestamp) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { + assert_eq!(*panic_data.at(0), TimeLock::Errors::ALREADY_QUEUED); + } + } +} + +#[test] +fn test_queue_timestamp_not_in_range() { + let timelock_test = TimeLockTestTrait::setup(); + match timelock_test.timelock_safe.queue(timelock_test.get_call(), 0) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { + assert_eq!(*panic_data.at(0), TimeLock::Errors::TIMESTAMP_NOT_IN_RANGE); + } + } + match timelock_test + .timelock_safe + .queue(timelock_test.get_call(), timelock_test.get_timestamp() + TimeLock::MAX_DELAY) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { + assert_eq!(*panic_data.at(0), TimeLock::Errors::TIMESTAMP_NOT_IN_RANGE); + } + } +} + +#[test] +fn test_queue_success() { + let timelock_test = TimeLockTestTrait::setup(); + let mut spy = spy_events(SpyOn::One(timelock_test.timelock_address)); + let timestamp = timelock_test.get_timestamp(); + let tx_id = timelock_test.timelock.queue(timelock_test.get_call(), timestamp); + spy + .assert_emitted( + @array![ + ( + timelock_test.timelock_address, + TimeLock::Event::Queue( + TimeLock::Queue { tx_id, call: timelock_test.get_call(), timestamp } + ) + ) + ] + ); + assert_eq!(tx_id, timelock_test.timelock.get_tx_id(timelock_test.get_call(), timestamp)); +} + +#[test] +fn test_execute_only_owner() { + let timelock_test = TimeLockTestTrait::setup(); + cheat_caller_address(timelock_test.timelock_address, OTHER(), CheatSpan::TargetCalls(1)); + match timelock_test + .timelock_safe + .execute(timelock_test.get_call(), timelock_test.get_timestamp()) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { assert_eq!(*panic_data.at(0), ownable::Errors::UNAUTHORIZED); } + } +} + +#[test] +fn test_execute_not_queued() { + let timelock_test = TimeLockTestTrait::setup(); + match timelock_test + .timelock_safe + .execute(timelock_test.get_call(), timelock_test.get_timestamp()) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { assert_eq!(*panic_data.at(0), TimeLock::Errors::NOT_QUEUED); } + } +} + +#[test] +fn test_execute_timestamp_not_passed() { + let timelock_test = TimeLockTestTrait::setup(); + let timestamp = timelock_test.get_timestamp(); + timelock_test.timelock.queue(timelock_test.get_call(), timestamp); + match timelock_test.timelock_safe.execute(timelock_test.get_call(), timestamp) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { + assert_eq!(*panic_data.at(0), TimeLock::Errors::TIMESTAMP_NOT_PASSED); + } + } +} + +#[test] +fn test_execute_timestamp_expired() { + let timelock_test = TimeLockTestTrait::setup(); + let timestamp = timelock_test.get_timestamp(); + timelock_test.timelock.queue(timelock_test.get_call(), timestamp); + cheat_block_timestamp( + timelock_test.timelock_address, + timestamp + TimeLock::GRACE_PERIOD + 1, + CheatSpan::TargetCalls(1) + ); + match timelock_test.timelock_safe.execute(timelock_test.get_call(), timestamp) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { + assert_eq!(*panic_data.at(0), TimeLock::Errors::TIMESTAMP_EXPIRED); + } + } +} + +#[test] +fn test_execute_success() { + let timelock_test = TimeLockTestTrait::setup(); + let timestamp = timelock_test.get_timestamp(); + let tx_id = timelock_test.timelock.get_tx_id(timelock_test.get_call(), timestamp); + timelock_test.timelock.queue(timelock_test.get_call(), timestamp); + timelock_test.erc721.approve(timelock_test.timelock_address, TOKEN_ID); + cheat_block_timestamp(timelock_test.timelock_address, timestamp + 1, CheatSpan::TargetCalls(1)); + let mut spy = spy_events(SpyOn::One(timelock_test.timelock_address)); + timelock_test.timelock.execute(timelock_test.get_call(), timestamp); + spy + .assert_emitted( + @array![ + ( + timelock_test.timelock_address, + TimeLock::Event::Execute( + TimeLock::Execute { tx_id, call: timelock_test.get_call(), timestamp } + ) + ) + ] + ); +} + +#[test] +fn test_execute_failed() { + let timelock_test = TimeLockTestTrait::setup(); + let timestamp = timelock_test.get_timestamp(); + timelock_test.timelock.queue(timelock_test.get_call(), timestamp); + cheat_block_timestamp(timelock_test.timelock_address, timestamp + 1, CheatSpan::TargetCalls(1)); + match timelock_test.timelock_safe.execute(timelock_test.get_call(), timestamp) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { + assert_eq!(*panic_data.at(0), ERC721Component::Errors::UNAUTHORIZED); + } + } +} + +#[test] +fn test_cancel_only_owner() { + let timelock_test = TimeLockTestTrait::setup(); + let tx_id = timelock_test + .timelock + .get_tx_id(timelock_test.get_call(), timelock_test.get_timestamp()); + cheat_caller_address(timelock_test.timelock_address, OTHER(), CheatSpan::TargetCalls(1)); + match timelock_test.timelock_safe.cancel(tx_id) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { assert_eq!(*panic_data.at(0), ownable::Errors::UNAUTHORIZED); } + } +} + +#[test] +fn test_cancel_not_queued() { + let timelock_test = TimeLockTestTrait::setup(); + let tx_id = timelock_test + .timelock + .get_tx_id(timelock_test.get_call(), timelock_test.get_timestamp()); + match timelock_test.timelock_safe.cancel(tx_id) { + Result::Ok(_) => panic_with_felt252('FAIL'), + Result::Err(panic_data) => { assert_eq!(*panic_data.at(0), TimeLock::Errors::NOT_QUEUED); } + } +} + +#[test] +fn test_cancel_success() { + let timelock_test = TimeLockTestTrait::setup(); + let tx_id = timelock_test + .timelock + .queue(timelock_test.get_call(), timelock_test.get_timestamp()); + let mut spy = spy_events(SpyOn::One(timelock_test.timelock_address)); + timelock_test.timelock.cancel(tx_id); + spy + .assert_emitted( + @array![ + ( + timelock_test.timelock_address, + TimeLock::Event::Cancel(TimeLock::Cancel { tx_id }) + ) + ] + ); +} diff --git a/listings/applications/timelock/src/tests/utils.cairo b/listings/applications/timelock/src/tests/utils.cairo new file mode 100644 index 00000000..c8de72a5 --- /dev/null +++ b/listings/applications/timelock/src/tests/utils.cairo @@ -0,0 +1,68 @@ +use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; +use starknet::account::Call; +use snforge_std::{declare, ContractClassTrait, test_address}; +use openzeppelin::utils::serde::SerializedAppend; +use openzeppelin::token::erc721::interface::IERC721Dispatcher; +use timelock::timelock::{TimeLock, ITimeLockDispatcher, ITimeLockSafeDispatcher}; + +pub const TOKEN_ID: u256 = 1; + +pub fn NAME() -> ByteArray { + "NAME" +} + +pub fn SYMBOL() -> ByteArray { + "SYMBOL" +} + +pub fn BASE_URI() -> ByteArray { + "https://api.example.com/v1/" +} + +pub fn OTHER() -> ContractAddress { + contract_address_const::<'OTHER'>() +} + +#[derive(Copy, Drop)] +pub struct TimeLockTest { + pub timelock_address: ContractAddress, + pub timelock: ITimeLockDispatcher, + pub timelock_safe: ITimeLockSafeDispatcher, + pub erc721_address: ContractAddress, + pub erc721: IERC721Dispatcher, +} + +#[generate_trait] +pub impl TimeLockTestImpl of TimeLockTestTrait { + fn setup() -> TimeLockTest { + let timelock_contract = declare("TimeLock").unwrap(); + let mut timelock_calldata = array![]; + let (timelock_address, _) = timelock_contract.deploy(@timelock_calldata).unwrap(); + let timelock = ITimeLockDispatcher { contract_address: timelock_address }; + let timelock_safe = ITimeLockSafeDispatcher { contract_address: timelock_address }; + let erc721_contract = declare("ERC721").unwrap(); + let mut erc721_calldata = array![]; + erc721_calldata.append_serde(NAME()); + erc721_calldata.append_serde(SYMBOL()); + erc721_calldata.append_serde(BASE_URI()); + erc721_calldata.append_serde(test_address()); + erc721_calldata.append_serde(TOKEN_ID); + let (erc721_address, _) = erc721_contract.deploy(@erc721_calldata).unwrap(); + let erc721 = IERC721Dispatcher { contract_address: erc721_address }; + TimeLockTest { timelock_address, timelock, timelock_safe, erc721_address, erc721 } + } + fn get_call(self: @TimeLockTest) -> Call { + let mut calldata = array![]; + calldata.append_serde(test_address()); + calldata.append_serde(*self.timelock_address); + calldata.append_serde(TOKEN_ID); + Call { + to: *self.erc721_address, + selector: selector!("transfer_from"), + calldata: calldata.span() + } + } + fn get_timestamp(self: @TimeLockTest) -> u64 { + get_block_timestamp() + TimeLock::MIN_DELAY + } +} diff --git a/listings/applications/timelock/src/timelock.cairo b/listings/applications/timelock/src/timelock.cairo new file mode 100644 index 00000000..29081790 --- /dev/null +++ b/listings/applications/timelock/src/timelock.cairo @@ -0,0 +1,155 @@ +use starknet::ContractAddress; +use starknet::account::Call; + +#[starknet::interface] +pub trait ITimeLock { + fn get_tx_id(self: @TState, call: Call, timestamp: u64) -> felt252; + fn queue(ref self: TState, call: Call, timestamp: u64) -> felt252; + fn execute(ref self: TState, call: Call, timestamp: u64) -> Span; + fn cancel(ref self: TState, tx_id: felt252); +} + +#[starknet::contract] +pub mod TimeLock { + use core::poseidon::{PoseidonTrait, poseidon_hash_span}; + use core::hash::{HashStateTrait, HashStateExTrait}; + use starknet::{ + ContractAddress, get_caller_address, get_block_timestamp, SyscallResultTrait, syscalls + }; + use starknet::account::Call; + use components::ownable::ownable_component; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + + // Ownable + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: ownable_component::Storage, + queued: LegacyMap::, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: ownable_component::Event, + Queue: Queue, + Execute: Execute, + Cancel: Cancel + } + + #[derive(Drop, starknet::Event)] + pub struct Queue { + #[key] + pub tx_id: felt252, + pub call: Call, + pub timestamp: u64 + } + + #[derive(Drop, starknet::Event)] + pub struct Execute { + #[key] + pub tx_id: felt252, + pub call: Call, + pub timestamp: u64 + } + + #[derive(Drop, starknet::Event)] + pub struct Cancel { + #[key] + pub tx_id: felt252 + } + + pub const MIN_DELAY: u64 = 10; // seconds + pub const MAX_DELAY: u64 = 1000; // seconds + pub const GRACE_PERIOD: u64 = 1000; // seconds + + pub mod Errors { + pub const ALREADY_QUEUED: felt252 = 'TimeLock: already queued'; + pub const TIMESTAMP_NOT_IN_RANGE: felt252 = 'TimeLock: timestamp range'; + pub const NOT_QUEUED: felt252 = 'TimeLock: not queued'; + pub const TIMESTAMP_NOT_PASSED: felt252 = 'TimeLock: timestamp not passed'; + pub const TIMESTAMP_EXPIRED: felt252 = 'TimeLock: timestamp expired'; + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.ownable._init(get_caller_address()); + } + + #[abi(embed_v0)] + impl TimeLockImpl of super::ITimeLock { + fn get_tx_id(self: @ContractState, call: Call, timestamp: u64) -> felt252 { + PoseidonTrait::new() + .update(call.to.into()) + .update(call.selector.into()) + .update(poseidon_hash_span(call.calldata)) + .update(timestamp.into()) + .finalize() + } + + fn queue(ref self: ContractState, call: Call, timestamp: u64) -> felt252 { + self.ownable._assert_only_owner(); + + let tx_id = self.get_tx_id(self._copy_call(@call), timestamp); + assert(!self.queued.read(tx_id), Errors::ALREADY_QUEUED); + // ---|------------|---------------|------- + // block block + min block + max + let block_timestamp = get_block_timestamp(); + assert( + timestamp >= block_timestamp + + MIN_DELAY && timestamp <= block_timestamp + + MAX_DELAY, + Errors::TIMESTAMP_NOT_IN_RANGE + ); + + self.queued.write(tx_id, true); + self.emit(Queue { tx_id, call: self._copy_call(@call), timestamp }); + + tx_id + } + + fn execute(ref self: ContractState, call: Call, timestamp: u64) -> Span { + self.ownable._assert_only_owner(); + + let tx_id = self.get_tx_id(self._copy_call(@call), timestamp); + assert(self.queued.read(tx_id), Errors::NOT_QUEUED); + // ----|-------------------|------- + // timestamp timestamp + grace period + let block_timestamp = get_block_timestamp(); + assert(block_timestamp >= timestamp, Errors::TIMESTAMP_NOT_PASSED); + assert(block_timestamp <= timestamp + GRACE_PERIOD, Errors::TIMESTAMP_EXPIRED); + + self.queued.write(tx_id, false); + + let result = syscalls::call_contract_syscall(call.to, call.selector, call.calldata) + .unwrap_syscall(); + + self.emit(Execute { tx_id, call: self._copy_call(@call), timestamp }); + + result + } + + fn cancel(ref self: ContractState, tx_id: felt252) { + self.ownable._assert_only_owner(); + + assert(self.queued.read(tx_id), Errors::NOT_QUEUED); + + self.queued.write(tx_id, false); + + self.emit(Cancel { tx_id }); + } + } + + #[generate_trait] + impl InternalImpl of InternalTrait { + fn _copy_call(self: @ContractState, call: @Call) -> Call { + Call { to: *call.to, selector: *call.selector, calldata: *call.calldata } + } + } +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index f76fdac3..799cb263 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -56,6 +56,7 @@ Summary - [Defi Vault](./ch01/simple_vault.md) - [ERC20 Token](./ch01/erc20.md) - [Constant Product AMM](./ch01/constant-product-amm.md) +- [TimeLock](./ch01/timelock.md) - [Staking](./ch01/staking.md) diff --git a/src/ch01/timelock.md b/src/ch01/timelock.md new file mode 100644 index 00000000..aaf73d08 --- /dev/null +++ b/src/ch01/timelock.md @@ -0,0 +1,7 @@ +# TimeLock + +This is the Cairo adaptation of the [Solidity by example TimeLock](https://solidity-by-example.org/app/time-lock/). + +```rust +{{#include ../../listings/applications/timelock/src/timelock.cairo}} +``` From 8cc51f5d2979d00a6e51714ae7a1ad7cd7310630 Mon Sep 17 00:00:00 2001 From: the-first-elder Date: Wed, 5 Jun 2024 07:35:59 +0100 Subject: [PATCH 14/36] test for upgradeable contracts (#203) * added erc20 unit test * added test against custom errors * formated * made requested changes and moved test to contract module * fixed zero address * chore: Remove unused test files * test for upgradeable contracts * fixed build issue * fix: pr#203 --------- Co-authored-by: julio4 --- .../constant_product_amm/src/lib.cairo | 2 +- listings/applications/erc20/src/token.cairo | 2 +- .../upgradeable_contract/src/tests.cairo | 86 ++++++++++++++++++- .../src/upgradeable_contract_v0.cairo | 12 +-- 4 files changed, 93 insertions(+), 9 deletions(-) diff --git a/listings/applications/constant_product_amm/src/lib.cairo b/listings/applications/constant_product_amm/src/lib.cairo index 0e88faff..a37a19db 100644 --- a/listings/applications/constant_product_amm/src/lib.cairo +++ b/listings/applications/constant_product_amm/src/lib.cairo @@ -1,4 +1,4 @@ -mod contracts; +pub mod contracts; #[cfg(test)] mod tests; diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index c2477789..7d04f8b8 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -249,7 +249,7 @@ mod tests { fn test_deploy_when_recipient_is_address_zero() { let recipient: ContractAddress = Zero::zero(); - let (_contract_address, _) = deploy_syscall( + let (contract_address, _) = deploy_syscall( erc20::TEST_CLASS_HASH.try_into().unwrap(), recipient.into(), array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), diff --git a/listings/applications/upgradeable_contract/src/tests.cairo b/listings/applications/upgradeable_contract/src/tests.cairo index 361dba07..3c3e7ee2 100644 --- a/listings/applications/upgradeable_contract/src/tests.cairo +++ b/listings/applications/upgradeable_contract/src/tests.cairo @@ -1,2 +1,86 @@ -mod tests { // TODO +mod tests { + use starknet::class_hash::ClassHash; + + use super::super::upgradeable_contract_v0::{ + UpgradeableContract_V0, IUpgradeableContractDispatcher as IUpgradeableContractDispatcher_v0, + IUpgradeableContractDispatcherTrait as UpgradeableContractDispatcherTrait_v0, + UpgradeableContract_V0::{Event, Upgraded} + }; + + use super::super::upgradeable_contract_v1::{ + UpgradeableContract_V1, IUpgradeableContractDispatcher as IUpgradeableContractDispatcher_v1, + IUpgradeableContractDispatcherTrait as UpgradeableContractDispatcherTrait_v1 + }; + + + use starknet::{ + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, + contract_address_const + }; + use core::num::traits::Zero; + + use starknet::testing::{set_contract_address, set_account_contract_address}; + + // deploy v0 contract + fn deploy_v0() -> (IUpgradeableContractDispatcher_v0, ContractAddress, ClassHash) { + let (contract_address, _) = deploy_syscall( + UpgradeableContract_V0::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ( + IUpgradeableContractDispatcher_v0 { contract_address }, + contract_address, + UpgradeableContract_V0::TEST_CLASS_HASH.try_into().unwrap() + ) + } + + // deploy v1 contract + fn deploy_v1() -> (IUpgradeableContractDispatcher_v1, ContractAddress, ClassHash) { + let (contract_address, _) = deploy_syscall( + UpgradeableContract_V1::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ( + IUpgradeableContractDispatcher_v1 { contract_address }, + contract_address, + UpgradeableContract_V1::TEST_CLASS_HASH.try_into().unwrap() + ) + } + + #[test] + fn test_deploy_v0() { + deploy_v0(); + } + + #[test] + fn test_deploy_v1() { + deploy_v1(); + } + + #[test] + fn test_version_from_v0() { + let (dispatcher, _, _) = deploy_v0(); + assert(dispatcher.version() == 0, 'incorrect version'); + } + + #[test] + #[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED'))] + fn test_upgrade_when_classhash_is_zero() { + let (dispatcher_v0, _, _) = deploy_v0(); + dispatcher_v0.upgrade(Zero::zero()); + } + + #[test] + fn test_succesful_upgrade_from_v0_to_v1() { + let (dispatcher_v0, contract_address, _) = deploy_v0(); + let (_, _, class_hash) = deploy_v1(); + dispatcher_v0.upgrade(class_hash); + // emit event + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(Event::Upgraded(Upgraded { implementation: class_hash })) + ); + + assert(dispatcher_v0.version() == 1, ' version did not upgrade'); + } } diff --git a/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo b/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo index c2aac9ca..e68d4251 100644 --- a/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo +++ b/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo @@ -18,14 +18,14 @@ pub mod UpgradeableContract_V0 { #[event] - #[derive(Drop, starknet::Event)] - enum Event { - Upgraded: Upgraded + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + Upgraded: Upgraded, } - #[derive(Drop, starknet::Event)] - struct Upgraded { - implementation: ClassHash + #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] + pub struct Upgraded { + pub implementation: ClassHash } #[abi(embed_v0)] From c0ab62fee1ab36b1ccb5127ad1a4876086e827b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Misi=C4=87?= Date: Wed, 5 Jun 2024 08:43:44 +0200 Subject: [PATCH 15/36] Components chapter: wording, grammar, formatting fixes (#210) * how-to fixes * Clean up dependencies * collisions > fixes * ownable minor fixes * remove error.log * fix typo * add comma after 'first' * add 'the' before cairo book --------- Co-authored-by: Nenad --- .../applications/components/src/ownable.cairo | 6 ++--- po/es.po | 12 +++++----- src/components/collisions.md | 4 +++- src/components/dependencies.md | 23 ++++++++----------- src/components/how_to.md | 14 ++++++----- src/components/ownable.md | 2 +- 6 files changed, 31 insertions(+), 30 deletions(-) diff --git a/listings/applications/components/src/ownable.cairo b/listings/applications/components/src/ownable.cairo index c73180e3..cca82ca5 100644 --- a/listings/applications/components/src/ownable.cairo +++ b/listings/applications/components/src/ownable.cairo @@ -67,17 +67,17 @@ pub mod ownable_component { > of OwnableInternalTrait { fn _assert_only_owner(self: @ComponentState) { let caller = get_caller_address(); - assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER); + assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER); assert(caller == self.ownable_owner.read(), Errors::UNAUTHORIZED); } fn _init(ref self: ComponentState, owner: ContractAddress) { - assert(!owner.is_zero(), Errors::ZERO_ADDRESS_OWNER); + assert(owner.is_non_zero(), Errors::ZERO_ADDRESS_OWNER); self.ownable_owner.write(owner); } fn _transfer_ownership(ref self: ComponentState, new: ContractAddress) { - assert(!new.is_zero(), Errors::ZERO_ADDRESS_OWNER); + assert(new.is_non_zero(), Errors::ZERO_ADDRESS_OWNER); let previous = self.ownable_owner.read(); self.ownable_owner.write(new); self diff --git a/po/es.po b/po/es.po index 8443d1e9..803c2c97 100644 --- a/po/es.po +++ b/po/es.po @@ -7139,20 +7139,20 @@ msgid "" " > of OwnableInternalTrait {\n" " fn _assert_only_owner(self: @ComponentState) {\n" " let caller = get_caller_address();\n" -" assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER);\n" +" assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER);\n" " assert(caller == self.ownable_owner.read(), Errors::" "UNAUTHORIZED);\n" " }\n" "\n" " fn _init(ref self: ComponentState, owner: " "ContractAddress) {\n" -" assert(!owner.is_zero(), Errors::ZERO_ADDRESS_OWNER);\n" +" assert(owner.is_non_zero(), Errors::ZERO_ADDRESS_OWNER);\n" " self.ownable_owner.write(owner);\n" " }\n" "\n" " fn _transfer_ownership(ref self: ComponentState, " "new: ContractAddress) {\n" -" assert(!new.is_zero(), Errors::ZERO_ADDRESS_OWNER);\n" +" assert(new.is_non_zero(), Errors::ZERO_ADDRESS_OWNER);\n" " let previous = self.ownable_owner.read();\n" " self.ownable_owner.write(new);\n" " self\n" @@ -7243,20 +7243,20 @@ msgstr "" " > of OwnableInternalTrait {\n" " fn _assert_only_owner(self: @ComponentState) {\n" " let caller = get_caller_address();\n" -" assert(!caller.is_zero(), Errors::ZERO_ADDRESS_CALLER);\n" +" assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER);\n" " assert(caller == self.ownable_owner.read(), Errors::" "UNAUTHORIZED);\n" " }\n" "\n" " fn _init(ref self: ComponentState, owner: " "ContractAddress) {\n" -" assert(!owner.is_zero(), Errors::ZERO_ADDRESS_OWNER);\n" +" assert(owner.is_non_zero(), Errors::ZERO_ADDRESS_OWNER);\n" " self.ownable_owner.write(owner);\n" " }\n" "\n" " fn _transfer_ownership(ref self: ComponentState, " "new: ContractAddress) {\n" -" assert(!new.is_zero(), Errors::ZERO_ADDRESS_OWNER);\n" +" assert(new.is_non_zero(), Errors::ZERO_ADDRESS_OWNER);\n" " let previous = self.ownable_owner.read();\n" " self.ownable_owner.write(new);\n" " self\n" diff --git a/src/components/collisions.md b/src/components/collisions.md index 28704755..5f8266be 100644 --- a/src/components/collisions.md +++ b/src/components/collisions.md @@ -2,7 +2,7 @@ Components can declare their own storage variables. -When a contract use a component, the component storage is merged with the contract storage. +When a contract uses a component, the component storage is merged with the contract storage. The storage layout is only determined by the variables names, so variables with the same name will collide. > In a future release, the `#[substorage(v1)]` will determine the storage layout based on the component as well, so collisions will be avoided. @@ -14,11 +14,13 @@ A good practice is to prefix the component storage variables with the component Here's an example of a collision on the `switchable_value` storage variable of the `Switchable` component. Interface: + ```rust {{#include ../../listings/applications/components/src/contracts/switch_collision.cairo:interface}} ``` Here's the storage of the contract (you can expand the code snippet to see the full contract): + ```rust {{#rustdoc_include ../../listings/applications/components/src/contracts/switch_collision.cairo:storage}} ``` diff --git a/src/components/dependencies.md b/src/components/dependencies.md index 42a363f8..08f20ec2 100644 --- a/src/components/dependencies.md +++ b/src/components/dependencies.md @@ -1,6 +1,6 @@ -# Components Dependencies +# Component Dependencies -A component with a dependency on a trait T can be used in a contract as long as the contract implements the trait T. +A component with a dependency on a trait `T` can be used in a contract as long as the contract implements the trait `T`. We will use a new `Countable` component as an example: @@ -8,13 +8,13 @@ We will use a new `Countable` component as an example: {{#include ../../listings/applications/components/src/countable.cairo}} ``` -We want to add a way to enable or disable the counter, in a way that calling `increment` on a disabled counter will not increment the counter. +We want to add a way to enable or disable the counter, in a way that calling `increment` on a disabled counter will not increment it. But we don't want to add this switch logic to the `Countable` component itself. -We instead add the trait `Switchable` as a dependency to the `Countable` component. +Instead, we add the trait `Switchable` as a dependency to the `Countable` component. #### Implementation of the trait in the contract -We first define the `ISwitchable` trait: +First, we import the `ISwitchable` trait defined in chapter ["Components How-To"](./how_to.md): ```rust {{#include ../../listings/applications/components/src/switchable.cairo:interface}} @@ -36,19 +36,19 @@ A contract that uses the `Countable` component must implement the `ISwitchable` In the previous example, we implemented the `ISwitchable` trait in the contract. -We already implemented a [`Switchable`](./how_to.md) component that provide an implementation of the `ISwitchable` trait. -By using the `Switchable` component in a contract, we embed the implementation of the `ISwitchable` trait in the contract and resolve the dependency on the `ISwitchable` trait. +We already implemented a [`Switchable`](./how_to.md) component that provides an implementation of the `ISwitchable` trait. +By using the `Switchable` component in a contract, we can embed the implementation of the `ISwitchable` trait in the contract and resolve the dependency on the `ISwitchable` trait. ```rust {{#rustdoc_include ../../listings/applications/components_dependencies/src/contract_countable_switchable.cairo:contract}} ``` -#### Dependency on other components internal functions +#### Dependency on other component's internal functions The previous example shows how to use the `ISwitchable` trait implementation from the `Switchable` component inside the `Countable` component by embedding the implementation in the contract. However, suppose we would like to turn off the switch after each increment. There's no `set` function in the `ISwitchable` trait, so we can't do it directly. -But the Switchable component implements the internal function `_off` from the `SwitchableInternalTrait` that set the switch to `false`. +But the `Switchable` component implements the internal function `_off` from the `SwitchableInternalTrait` that set the switch to `false`. We can't embed `SwitchableInternalImpl`, but we can add `switchable::HasComponent` as a dependency inside `CountableImpl`. We make the `Countable` component depend on the `Switchable` component. @@ -58,7 +58,4 @@ This will allow to do `switchable::ComponentState` -> `TContract {{#rustdoc_include ../../listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo:contract}} ``` -The contract remains the same that the previous example, but the implementation of the `Countable` component is different: -```rust -{{#rustdoc_include ../../listings/applications/components_dependencies/src/contract_countable_switchable_internal.cairo:contract}} -``` +The `CountableContract` contract remains the same as in the previous example, only the implementation of the `Countable` component is different. diff --git a/src/components/how_to.md b/src/components/how_to.md index e2d0fe00..41e18f86 100644 --- a/src/components/how_to.md +++ b/src/components/how_to.md @@ -5,6 +5,7 @@ They are used to separate the core logic from common functionalities, simplifyin It also reduces the risk of bugs and vulnerabilities by using well-tested components. Key characteristics: + - Modularity: Easily pluggable into multiple contracts. - Reusable Logic: Encapsulates specific functionalities. - Not Standalone: Cannot be declared or deployed independently. @@ -20,19 +21,20 @@ It contains a storage variable `switchable_value`, a function `switch` and an ev {{#include ../../listings/applications/components/src/switchable.cairo:component}} ``` -A component in itself is really similar to a contract, it *can* also have: +A component is really similar to a contract and can also have: + - An interface defining entrypoints (`ISwitchableComponent`) - A Storage struct - Events - Internal functions -It don't have a constructor, but you can create a `_init` internal function and call it from the contract's constructor. In the previous example, the `_off` function is used this way. +It doesn't have a constructor, but you can create an `_init` internal function and call it from the contract's constructor. In the previous example, the `_off` function will be used this way. > It's currently not possible to use the same component multiple times in the same contract. > This is a known limitation that may be lifted in the future. -> -> For now, you can view components as an implementation of a specific interface/feature (`Ownable`, `Upgradeable`, ... `~able`). -> This is why we called it `Switchable` and not `Switch`; The contract *is switchable*, not *has a switch*. +> +> For now, you can view components as implementations of a specific interfaces or features (`Ownable`, `Upgradeable`, ... `~able`). +> This is why we called the component in the above example `Switchable`, and not `Switch`; the contract _is switchable_, it does not _have a switch_. ## How to use a component @@ -45,4 +47,4 @@ The following contract incorporates the `Switchable` component: ## Deep dive into components -You can find more in-depth information about components in the [Cairo book - Components](https://book.cairo-lang.org/ch99-01-05-00-components.html). +You can find more in-depth information about components in [The Cairo book - Components](https://book.cairo-lang.org/ch16-02-00-composability-and-components.html). diff --git a/src/components/ownable.md b/src/components/ownable.md index 38fc7b19..003ccb9a 100644 --- a/src/components/ownable.md +++ b/src/components/ownable.md @@ -1,6 +1,6 @@ # Ownable -The following `Ownable` component is a simple component that allows the contract to set an owner and provides a `_assert_is_owner` function that can be used to ensure that the caller is the owner. +The following `Ownable` component is a simple component that allows the contract to set an owner and provides an `_assert_is_owner` function that can be used to ensure that the caller is the owner. It can also be used to renounce ownership of a contract, meaning that no one will be able to satisfy the `_assert_is_owner` function. From 5e758e6bf442946ad8357028b11941c97117e7d3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Misi=C4=87?= Date: Sat, 8 Jun 2024 10:08:20 +0200 Subject: [PATCH 16/36] Advanced concepts: typos, wording, grammar, formatting (#215) * formatting for write to slot * stor. arrs. > wording, grammar * hashing > wording, grammar * packing > wording, grammar, format * list > amount->amounts, wording, typos * plugins > grammar * sign. verif. > grammar * remove 'a' in how_to * update library calls -> dispatcher * Revert "update library calls -> dispatcher" This reverts commit e7d3b0c23d40e81419327e8d1b42a2ad839926e6. * align library calls header * fix tests --------- Co-authored-by: Nenad --- .../store_using_packing/src/contract.cairo | 4 +-- .../storing_arrays/src/contract.cairo | 7 ++--- .../using_lists/src/contract.cairo | 16 +++++----- .../using_lists/src/tests.cairo | 6 ++-- po/es.po | 30 +++++++++---------- po/zh-cn.po | 30 +++++++++---------- src/ch02/hashing.md | 6 ++-- src/ch02/library_calls.md | 5 +--- src/ch02/list.md | 13 ++++---- src/ch02/optimisations/store_using_packing.md | 8 ++--- src/ch02/plugins.md | 4 +-- src/ch02/signature_verification.md | 2 +- src/ch02/storing_arrays.md | 4 +-- src/ch02/write_to_any_slot.md | 2 +- src/components/how_to.md | 2 +- 15 files changed, 68 insertions(+), 71 deletions(-) diff --git a/listings/advanced-concepts/store_using_packing/src/contract.cairo b/listings/advanced-concepts/store_using_packing/src/contract.cairo index 5cdb79b5..5f124ac2 100644 --- a/listings/advanced-concepts/store_using_packing/src/contract.cairo +++ b/listings/advanced-concepts/store_using_packing/src/contract.cairo @@ -24,14 +24,14 @@ pub mod TimeContract { fn pack(value: Time) -> felt252 { let msb: felt252 = 256 * value.hour.into(); let lsb: felt252 = value.minute.into(); - return msb + lsb; + msb + lsb } fn unpack(value: felt252) -> Time { let value: u16 = value.try_into().unwrap(); let (q, r) = DivRem::div_rem(value, 256_u16.try_into().unwrap()); let hour: u8 = Into::::into(q).try_into().unwrap(); let minute: u8 = Into::::into(r).try_into().unwrap(); - return Time { hour, minute }; + Time { hour, minute } } } diff --git a/listings/advanced-concepts/storing_arrays/src/contract.cairo b/listings/advanced-concepts/storing_arrays/src/contract.cairo index 949e0e8a..991631be 100644 --- a/listings/advanced-concepts/storing_arrays/src/contract.cairo +++ b/listings/advanced-concepts/storing_arrays/src/contract.cairo @@ -1,7 +1,6 @@ use starknet::SyscallResultTrait; use starknet::{Store, SyscallResult}; -use starknet::storage_access::{StorageBaseAddress, storage_address_from_base_and_offset}; -use starknet::syscalls::{storage_read_syscall, storage_write_syscall}; +use starknet::storage_access::StorageBaseAddress; // ANCHOR: StorageAccessImpl impl StoreFelt252Array of Store> { @@ -20,7 +19,7 @@ impl StoreFelt252Array of Store> { ) -> SyscallResult> { let mut arr: Array = array![]; - // Read the stored array's length. If the length is superior to 255, the read will fail. + // Read the stored array's length. If the length is greater than 255, the read will fail. let len: u8 = Store::::read_at_offset(address_domain, base, offset) .expect('Storage Span too large'); offset += 1; @@ -44,7 +43,7 @@ impl StoreFelt252Array of Store> { fn write_at_offset( address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array ) -> SyscallResult<()> { - // // Store the length of the array in the first storage slot. + // Store the length of the array in the first storage slot. let len: u8 = value.len().try_into().expect('Storage - Span too large'); Store::::write_at_offset(address_domain, base, offset, len).unwrap(); offset += 1; diff --git a/listings/advanced-concepts/using_lists/src/contract.cairo b/listings/advanced-concepts/using_lists/src/contract.cairo index 35101fa8..9ee274db 100644 --- a/listings/advanced-concepts/using_lists/src/contract.cairo +++ b/listings/advanced-concepts/using_lists/src/contract.cairo @@ -23,14 +23,14 @@ pub mod ListExample { #[storage] pub struct Storage { - amount: List, + amounts: List, tasks: List } #[abi(embed_v0)] impl ListExample of super::IListExample { fn add_in_amount(ref self: ContractState, number: u128) { - let mut current_amount_list = self.amount.read(); + let mut current_amount_list = self.amounts.read(); current_amount_list.append(number).unwrap(); } @@ -41,31 +41,31 @@ pub mod ListExample { } fn is_empty_list(self: @ContractState) -> bool { - let mut current_amount_list = self.amount.read(); + let mut current_amount_list = self.amounts.read(); current_amount_list.is_empty() } fn list_length(self: @ContractState) -> u32 { - let mut current_amount_list = self.amount.read(); + let mut current_amount_list = self.amounts.read(); current_amount_list.len() } fn get_from_index(self: @ContractState, index: u32) -> u128 { - self.amount.read()[index] + self.amounts.read()[index] } fn set_from_index(ref self: ContractState, index: u32, number: u128) { - let mut current_amount_list = self.amount.read(); + let mut current_amount_list = self.amounts.read(); current_amount_list.set(index, number).unwrap(); } fn pop_front_list(ref self: ContractState) { - let mut current_amount_list = self.amount.read(); + let mut current_amount_list = self.amounts.read(); current_amount_list.pop_front().unwrap().unwrap(); } fn array_conversion(self: @ContractState) -> Array { - let mut current_amount_list = self.amount.read(); + let mut current_amount_list = self.amounts.read(); current_amount_list.array().unwrap() } } diff --git a/listings/advanced-concepts/using_lists/src/tests.cairo b/listings/advanced-concepts/using_lists/src/tests.cairo index 8ce5c390..98f667c3 100644 --- a/listings/advanced-concepts/using_lists/src/tests.cairo +++ b/listings/advanced-concepts/using_lists/src/tests.cairo @@ -1,7 +1,7 @@ use using_lists::contract::IListExample; use using_lists::contract::{Task, ListExample}; use using_lists::contract::ListExample::{ - amountContractMemberStateTrait, tasksContractMemberStateTrait + amountsContractMemberStateTrait, tasksContractMemberStateTrait }; fn STATE() -> ListExample::ContractState { @@ -13,7 +13,7 @@ fn STATE() -> ListExample::ContractState { fn test_add_in_amount() { let mut state = STATE(); state.add_in_amount(200); - assert(state.amount.read()[0] == 200, 'should be 200'); + assert(state.amounts.read()[0] == 200, 'should be 200'); } #[test] @@ -67,7 +67,7 @@ fn test_set_from_index() { let mut state = STATE(); state.add_in_amount(200); state.set_from_index(0, 400); - assert(state.amount.read()[0] == 400, 'should be 400'); + assert(state.amounts.read()[0] == 400, 'should be 400'); } #[test] diff --git a/po/es.po b/po/es.po index 803c2c97..3adf59e9 100644 --- a/po/es.po +++ b/po/es.po @@ -10840,7 +10840,7 @@ msgid "" "\n" " #[storage]\n" " struct Storage {\n" -" amount: List,\n" +" amounts: List,\n" " tasks: List\n" " }\n" "\n" @@ -10854,7 +10854,7 @@ msgid "" " #[abi(embed_v0)]\n" " impl ListExample of super::IListExample {\n" " fn add_in_amount(ref self: ContractState, number: u128) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.append(number);\n" " }\n" "\n" @@ -10867,32 +10867,32 @@ msgid "" " }\n" "\n" " fn is_empty_list(self: @ContractState) -> bool {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.is_empty()\n" " }\n" "\n" " fn list_length(self: @ContractState) -> u32 {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.len()\n" " }\n" "\n" " fn get_from_index(self: @ContractState, index: u32) -> u128 {\n" -" self.amount.read()[index]\n" +" self.amounts.read()[index]\n" " }\n" "\n" " fn set_from_index(ref self: ContractState, index: u32, number: u128) " "{\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.set(index, number);\n" " }\n" "\n" " fn pop_front_list(ref self: ContractState) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.pop_front();\n" " }\n" "\n" " fn array_conversion(self: @ContractState) -> Array {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.array()\n" " }\n" " }\n" @@ -10933,7 +10933,7 @@ msgstr "" " #[abi(embed_v0)]\n" " impl ListExample of super::IListExample {\n" " fn add_in_amount(ref self: ContractState, number: u128) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.append(number);\n" " }\n" "\n" @@ -10946,32 +10946,32 @@ msgstr "" " }\n" "\n" " fn is_empty_list(self: @ContractState) -> bool {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.is_empty()\n" " }\n" "\n" " fn list_length(self: @ContractState) -> u32 {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.len()\n" " }\n" "\n" " fn get_from_index(self: @ContractState, index: u32) -> u128 {\n" -" self.amount.read()[index]\n" +" self.amounts.read()[index]\n" " }\n" "\n" " fn set_from_index(ref self: ContractState, index: u32, number: u128) " "{\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.set(index, number);\n" " }\n" "\n" " fn pop_front_list(ref self: ContractState) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.pop_front();\n" " }\n" "\n" " fn array_conversion(self: @ContractState) -> Array {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.array()\n" " }\n" " }\n" diff --git a/po/zh-cn.po b/po/zh-cn.po index 90953985..3ff5d71c 100644 --- a/po/zh-cn.po +++ b/po/zh-cn.po @@ -6945,7 +6945,7 @@ msgid "" "\n" " #[storage]\n" " struct Storage {\n" -" amount: List,\n" +" amounts: List,\n" " tasks: List\n" " }\n" "\n" @@ -6959,7 +6959,7 @@ msgid "" " #[abi(embed_v0)]\n" " impl ListExample of super::IListExample {\n" " fn add_in_amount(ref self: ContractState, number: u128) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.append(number);\n" " }\n" "\n" @@ -6972,32 +6972,32 @@ msgid "" " }\n" "\n" " fn is_empty_list(self: @ContractState) -> bool {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.is_empty()\n" " }\n" "\n" " fn list_length(self: @ContractState) -> u32 {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.len()\n" " }\n" "\n" " fn get_from_index(self: @ContractState, index: u32) -> u128 {\n" -" self.amount.read()[index]\n" +" self.amounts.read()[index]\n" " }\n" "\n" " fn set_from_index(ref self: ContractState, index: u32, number: u128) " "{\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.set(index, number);\n" " }\n" "\n" " fn pop_front_list(ref self: ContractState) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.pop_front();\n" " }\n" "\n" " fn array_conversion(self: @ContractState) -> Array {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.array()\n" " }\n" " }\n" @@ -7038,7 +7038,7 @@ msgstr "" " #[abi(embed_v0)]\n" " impl ListExample of super::IListExample {\n" " fn add_in_amount(ref self: ContractState, number: u128) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.append(number);\n" " }\n" "\n" @@ -7051,32 +7051,32 @@ msgstr "" " }\n" "\n" " fn is_empty_list(self: @ContractState) -> bool {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.is_empty()\n" " }\n" "\n" " fn list_length(self: @ContractState) -> u32 {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.len()\n" " }\n" "\n" " fn get_from_index(self: @ContractState, index: u32) -> u128 {\n" -" self.amount.read()[index]\n" +" self.amounts.read()[index]\n" " }\n" "\n" " fn set_from_index(ref self: ContractState, index: u32, number: u128) " "{\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.set(index, number);\n" " }\n" "\n" " fn pop_front_list(ref self: ContractState) {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.pop_front();\n" " }\n" "\n" " fn array_conversion(self: @ContractState) -> Array {\n" -" let mut current_amount_list = self.amount.read();\n" +" let mut current_amount_list = self.amounts.read();\n" " current_amount_list.array()\n" " }\n" " }\n" diff --git a/src/ch02/hashing.md b/src/ch02/hashing.md index ad6377c3..54c233d8 100644 --- a/src/ch02/hashing.md +++ b/src/ch02/hashing.md @@ -5,11 +5,11 @@ The resulting output is called a hash and it's completely different from the inp Hash functions are deterministic, meaning that the same input will always produce the same output. The two hash functions provided by the Cairo library are `Poseidon` and `Pedersen`. -Pedersen hashes were used in the past (but still used in some scenario for backward compatibility) while Poseidon hashes are the standard nowadays since they were designed to be very efficient for Zero Knowledge proof systems. +Pedersen hashes were used in the past (but are still used in some scenarios for backward compatibility), while Poseidon hashes are the standard nowadays since they were designed to be very efficient for Zero Knowledge proof systems. -In Cairo it's possible to hash all the types that can be converted to `felt252` since they implement natively the `Hash` trait. It's also possible to hash more complex types like structs by deriving the Hash trait with the attribute `#[derive(Hash)]` but only if all the struct's fields are themselves hashable. +In Cairo, it's possible to hash all types that can be converted to `felt252` since they natively implement the `Hash` trait. It's also possible to hash more complex types, like structs, by deriving the Hash trait with the `#[derive(Hash)]` attribute, but only if all the struct's fields are themselves hashable. -You first need to initialize a hash state with the `new` method of the `HashStateTrait` and then you can update it with the `update` method. You can accumulate multiple updates. Then, the `finalize` method returns the final hash value as a `felt252`. +To hash a value, you first need to initialize a hash state with the `new` method of the `HashStateTrait`. Then, you can update the hash state with the `update` method. You can accumulate multiple updates if necessary. Finally, the `finalize` method returns the final hash value as a `felt252`. ```rust {{#rustdoc_include ../../listings/advanced-concepts/hash_trait/src/hash_trait.cairo:hash}} diff --git a/src/ch02/library_calls.md b/src/ch02/library_calls.md index 85813bba..f33aff17 100644 --- a/src/ch02/library_calls.md +++ b/src/ch02/library_calls.md @@ -1,4 +1,4 @@ -# Library Dispatcher +# Library Calls External calls can be made on Starknet by two means: Contract dispatchers or Library dispatchers. Dispatchers are automatically created and exported by the compiler when a contract interface is defined. @@ -11,6 +11,3 @@ For further reading: [Cairo book](https://book.cairo-lang.org/ch15-02-contract-d ```rust {{#rustdoc_include ../../listings/advanced-concepts/library_calls/src/library_call.cairo:library_dispatcher}} ``` - - - diff --git a/src/ch02/list.md b/src/ch02/list.md index 637f6123..3bcc5fa1 100644 --- a/src/ch02/list.md +++ b/src/ch02/list.md @@ -1,6 +1,6 @@ # List -By default, there is no list type supported in Cairo, but you can use Alexandria. You can refer to the [Alexandria documentation](https://github.com/keep-starknet-strange/alexandria/tree/main/src/storage) for more details. +By default, there is no list type supported in Cairo, but you can use the Alexandria standard library. You can refer to the [Alexandria documentation](https://github.com/keep-starknet-strange/alexandria/tree/main/packages/storage) for more details. ## What is `List`? @@ -37,13 +37,13 @@ Note that unlike `get`, using this bracket notation panics when accessing an out ### Support for custom types -`List` supports most of the corelib types out of the box. If you want to store a your own custom type in a `List`, it has to implement the `Store` trait. You can have the compiler derive it for you using the `#[derive(starknet::Store)]` attribute. +`List` supports most of the corelib types out of the box. If you want to store your own custom type in a `List`, you have to implement the `Store` trait for it. You can have the compiler derive it for you using the `#[derive(starknet::Store)]` attribute. ### Caveats -There are two idiosyncacies you should be aware of when using `List` +There are two idiosyncrasies you should be aware of when using `List`: -1. The `append` operation costs 2 storage writes - one for the value itself and another one for updating the List's length +1. The `append` operation costs 2 storage writes - one for the value itself, and another one for updating the List's length 2. Due to a compiler limitation, it is not possible to use mutating operations with a single inline statement. For example, `self.amounts.read().append(42);` will not work. You have to do it in 2 steps: ```rust @@ -53,7 +53,8 @@ amounts.append(42); ### Dependencies -Update your project dependencies by in the `Scarb.toml` file: +Update your project dependencies in the `Scarb.toml` file: + ```rust [dependencies] (...) @@ -64,4 +65,4 @@ For example, let's use `List` to create a contract that tracks a list of amounts ```rust {{#include ../../listings/advanced-concepts/using_lists/src/contract.cairo}} -``` \ No newline at end of file +``` diff --git a/src/ch02/optimisations/store_using_packing.md b/src/ch02/optimisations/store_using_packing.md index 8b736797..c9317604 100644 --- a/src/ch02/optimisations/store_using_packing.md +++ b/src/ch02/optimisations/store_using_packing.md @@ -1,4 +1,4 @@ -# Storage optimisation +# Storage optimisation A smart contract has a limited amount of **storage slots**. Each slot can store a single `felt252` value. Writing to a storage slot has a cost, so we want to use as few storage slots as possible. @@ -8,9 +8,9 @@ This design is quite simple, but it does have a drawback: it is not storage effi ## Packing -When storing multiple values, we can use a technique called **packing**. Packing is a technique that allows us to store multiple values in a single felt value. This is done by using the bits of the felt value to store multiple values. +When storing multiple values, we can use a technique called **packing**. Packing is a technique that allows us to store multiple values in a single `felt252` value. This is done by using the bits of the `felt252` value to store multiple values. -For example, if we want to store two `u8` values, we can use the first 8 bits of the felt value to store the first `u8` value, and the last 8 bits to store the second `u8` value. This way, we can store two `u8` values in a single felt value. +For example, if we want to store two `u8` values, we can use the first 8 bits of the `felt252` value to store the first `u8` value, and the last 8 bits to store the second `u8` value. This way, we can store two `u8` values in a single `felt252` value. Cairo provides a built-in store using packing that you can use with the `StorePacking` trait. @@ -21,7 +21,7 @@ trait StorePacking { } ``` -This allows to store the type `T` by first packing it into the type `PackedT` with the `pack` function, and then storing the `PackedT` value with it's `Store` implementation. When reading the value, we first retrieve the `PackedT` value, and then unpack it into the type `T` using the `unpack` function. +This allows us to store the type `T` by first packing it into the type `PackedT` with the `pack` function, and then storing the `PackedT` value with it's `Store` implementation. When reading the value, we first retrieve the `PackedT` value, and then unpack it into the type `T` using the `unpack` function. Here's an example of storing a `Time` struct with two `u8` values using the `StorePacking` trait: diff --git a/src/ch02/plugins.md b/src/ch02/plugins.md index 355756b6..b037ab84 100644 --- a/src/ch02/plugins.md +++ b/src/ch02/plugins.md @@ -1,7 +1,7 @@ # Plugins -Compilers plugin in Cairo are a way to generate additional code for specific items during the compilation process. +Compiler plugins in Cairo are a way to generate additional code for specific items during the compilation process. There are already a set of core plugins that you may have already used, such as `#[derive]`, `#[cfg]`, `#[generate_trait]`, etc. -It is possible to create your own plugins in rust. You can learn more about how to do that in the [hello-cairo-plugin](https://github.com/piwonskp/hello-cairo-plugin) repository. +It is possible to create your own plugins in Cairo. You can learn more about how to do that in the [hello-cairo-plugin](https://github.com/piwonskp/hello-cairo-plugin) repository. diff --git a/src/ch02/signature_verification.md b/src/ch02/signature_verification.md index 3cbd8dc6..7c51ca50 100644 --- a/src/ch02/signature_verification.md +++ b/src/ch02/signature_verification.md @@ -1,6 +1,6 @@ # ECDSA Verification -This is the Cairo adaptation of the [Solidity by example Verifying Signature](https://solidity-by-example.org/signature/) +This is the Cairo adaptation of the [Solidity by Example - Verifying Signature](https://solidity-by-example.org/signature/). Messages can be signed off chain and then verified on chain using a smart contract. ```rust diff --git a/src/ch02/storing_arrays.md b/src/ch02/storing_arrays.md index b3bfeb77..d980a7f4 100644 --- a/src/ch02/storing_arrays.md +++ b/src/ch02/storing_arrays.md @@ -1,8 +1,8 @@ # Storing Arrays -On Starknet, complex values (e.g., tuples or structs), are stored in a continuous segment starting from the address of the storage variable. There is a 256 field elements limitation to the maximal size of a complex storage value, meaning that to store arrays of more than 255 elements in storage, we would need to split it into segments of size `n <= 255` and store these segments in multiple storage addresses. There is currently no native support for storing arrays in Cairo, so you will need to write your own implementation of the `Store` trait for the type of array you wish to store. +On Starknet, complex values (e.g. tuples or structs) are stored in a continuous segment starting from the address of the storage variable. There is a limitation in Cairo that restricts complex storage values to a maximum of 256 field elements. This means that to store arrays with more than 255 elements, you would have to split them into segments of size `n <= 255` and store these segments at multiple storage addresses. There is currently no native support for storing arrays in Cairo, so you would need to write your own implementation of the `Store` trait for the array type you wish to store. -However, the `ByteArray` struct can be used to store `Array` in storage without additional implementation. Before implementing your own `Store` trait, consider wether the `ByteArray` struct can be used to store the data you need! See the [ByteArray](../ch00/basics/bytearrays-strings.md#bytearray-long-strings) section for more information. +However, the `ByteArray` struct can be used to store `Array` in storage without additional implementation. Before implementing your own `Store` trait, consider whether the `ByteArray` struct can be used to store the data you need! See the [ByteArray](../ch00/basics/bytearrays-strings.md#bytearray-long-strings) section for more information. > Note: While storing arrays in storage is possible, it is not always recommended, as the read and write operations can get very costly. For example, reading an array of size `n` requires `n` storage reads, and writing to an array of size `n` requires `n` storage writes. If you only need to access a single element of the array at a time, it is recommended to use a `LegacyMap` and store the length in another variable instead. diff --git a/src/ch02/write_to_any_slot.md b/src/ch02/write_to_any_slot.md index 9c677818..ef957757 100644 --- a/src/ch02/write_to_any_slot.md +++ b/src/ch02/write_to_any_slot.md @@ -1,6 +1,6 @@ # Writing to any storage slot -On Starknet, a contract's storage is a map with 2^251 slots, where each slot is a felt which is initialized to 0. +On Starknet, a contract's storage is a map with \\( 2^{251} \\) slots, where each slot is a `felt252` which is initialized to 0. The address of storage variables is computed at compile time using the formula: `storage variable address := pedersen(keccak(variable name), keys)`. Interactions with storage variables are commonly performed using the `self.var.read()` and `self.var.write()` functions. Nevertheless, we can use the `storage_write_syscall` and `storage_read_syscall` syscalls, to write to and read from any storage slot. diff --git a/src/components/how_to.md b/src/components/how_to.md index 41e18f86..30f8cbea 100644 --- a/src/components/how_to.md +++ b/src/components/how_to.md @@ -33,7 +33,7 @@ It doesn't have a constructor, but you can create an `_init` internal function a > It's currently not possible to use the same component multiple times in the same contract. > This is a known limitation that may be lifted in the future. > -> For now, you can view components as implementations of a specific interfaces or features (`Ownable`, `Upgradeable`, ... `~able`). +> For now, you can view components as implementations of specific interfaces or features (`Ownable`, `Upgradeable`, ... `~able`). > This is why we called the component in the above example `Switchable`, and not `Switch`; the contract _is switchable_, it does not _have a switch_. ## How to use a component From 19c67c20020d1b425b86857a2baf32568daf09bc Mon Sep 17 00:00:00 2001 From: princeibs <64266194+princeibs@users.noreply.github.com> Date: Sun, 9 Jun 2024 07:39:08 +0100 Subject: [PATCH 17/36] feat: nft dutch auction (#204) * feat: nft auction * test: add tests to nft_auction app * chore: improve code and add more tests * chore: improvements and more tests * test: add more test cases for nft_auction * chore: update mdbook * chore: update nft_auction package - Add error module - Update snforge version to 0.24.0 * chore: rename package and related files from `nft_auction` to `nft_dutch_auction` * chore: reused existing package * fix: cli#204 --------- Co-authored-by: julio4 --- Scarb.lock | 8 + Scarb.toml | 7 +- listings/applications/erc20/src/lib.cairo | 2 +- listings/applications/erc20/src/token.cairo | 2 +- .../applications/nft_dutch_auction/.gitignore | 1 + .../applications/nft_dutch_auction/Scarb.toml | 17 ++ .../nft_dutch_auction/src/erc721.cairo | 289 ++++++++++++++++++ .../nft_dutch_auction/src/lib.cairo | 5 + .../src/nft_dutch_auction.cairo | 135 ++++++++ .../nft_dutch_auction/src/tests.cairo | 215 +++++++++++++ src/SUMMARY.md | 1 + src/ch01/nft_dutch_auction.md | 14 + 12 files changed, 689 insertions(+), 7 deletions(-) create mode 100644 listings/applications/nft_dutch_auction/.gitignore create mode 100644 listings/applications/nft_dutch_auction/Scarb.toml create mode 100644 listings/applications/nft_dutch_auction/src/erc721.cairo create mode 100644 listings/applications/nft_dutch_auction/src/lib.cairo create mode 100644 listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo create mode 100644 listings/applications/nft_dutch_auction/src/tests.cairo create mode 100644 src/ch01/nft_dutch_auction.md diff --git a/Scarb.lock b/Scarb.lock index 31034f8c..ef3d064e 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -88,6 +88,14 @@ version = "0.1.0" name = "mappings" version = "0.1.0" +[[package]] +name = "nft_dutch_auction" +version = "0.1.0" +dependencies = [ + "erc20", + "snforge_std", +] + [[package]] name = "openzeppelin" version = "0.11.0" diff --git a/Scarb.toml b/Scarb.toml index 4a232645..4c5c042d 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -3,22 +3,19 @@ members = [ "listings/getting-started/*", "listings/applications/*", "listings/advanced-concepts/*", - "listings/templates/*" + "listings/templates/*", ] [workspace.scripts] test = "$(git rev-parse --show-toplevel)/scripts/test_resolver.sh" -# [workspace.tool.snforge] +[workspace.tool.snforge] [workspace.dependencies] starknet = ">=2.6.3" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.11.0" } components = { path = "listings/applications/components" } - -# [workspace.dev-dependencies] snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.24.0" } -# openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.11.0" } [workspace.package] description = "Collection of examples of how to use the Cairo programming language to create smart contracts on Starknet." diff --git a/listings/applications/erc20/src/lib.cairo b/listings/applications/erc20/src/lib.cairo index 40d3ff58..79c66ba6 100644 --- a/listings/applications/erc20/src/lib.cairo +++ b/listings/applications/erc20/src/lib.cairo @@ -1 +1 @@ -mod token; +pub mod token; diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index 7d04f8b8..c2477789 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -249,7 +249,7 @@ mod tests { fn test_deploy_when_recipient_is_address_zero() { let recipient: ContractAddress = Zero::zero(); - let (contract_address, _) = deploy_syscall( + let (_contract_address, _) = deploy_syscall( erc20::TEST_CLASS_HASH.try_into().unwrap(), recipient.into(), array![recipient.into(), token_name, decimals.into(), initial_supply, symbols].span(), diff --git a/listings/applications/nft_dutch_auction/.gitignore b/listings/applications/nft_dutch_auction/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/applications/nft_dutch_auction/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/applications/nft_dutch_auction/Scarb.toml b/listings/applications/nft_dutch_auction/Scarb.toml new file mode 100644 index 00000000..a6af238d --- /dev/null +++ b/listings/applications/nft_dutch_auction/Scarb.toml @@ -0,0 +1,17 @@ +[package] +name = "nft_dutch_auction" +version.workspace = true +edition = '2023_11' + +[dependencies] +erc20 = { path = "../erc20" } +starknet.workspace = true + +[dev-dependencies] +snforge_std.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +build-external-contracts = ["erc20::token::erc20"] diff --git a/listings/applications/nft_dutch_auction/src/erc721.cairo b/listings/applications/nft_dutch_auction/src/erc721.cairo new file mode 100644 index 00000000..9e1733ca --- /dev/null +++ b/listings/applications/nft_dutch_auction/src/erc721.cairo @@ -0,0 +1,289 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC721 { + fn get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; + fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); + fn transfer_from( + ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ); + fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); +} + +#[starknet::contract] +mod ERC721 { + //////////////////////////////// + // library imports + //////////////////////////////// + use starknet::{ContractAddress, get_caller_address}; + use core::traits::TryInto; + use core::num::traits::zero::Zero; + + //////////////////////////////// + // storage variables + //////////////////////////////// + #[storage] + struct Storage { + name: felt252, + symbol: felt252, + owners: LegacyMap::, + balances: LegacyMap::, + token_approvals: LegacyMap::, + operator_approvals: LegacyMap::<(ContractAddress, ContractAddress), bool>, + token_uri: LegacyMap, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + Approval: Approval, + Transfer: Transfer, + ApprovalForAll: ApprovalForAll + } + + //////////////////////////////// + // Approval event emitted on token approval + //////////////////////////////// + #[derive(Drop, starknet::Event)] + struct Approval { + owner: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + //////////////////////////////// + // Transfer event emitted on token transfer + //////////////////////////////// + #[derive(Drop, starknet::Event)] + struct Transfer { + from: ContractAddress, + to: ContractAddress, + token_id: u256 + } + + //////////////////////////////// + // ApprovalForAll event emitted on approval for operators + //////////////////////////////// + #[derive(Drop, starknet::Event)] + struct ApprovalForAll { + owner: ContractAddress, + operator: ContractAddress, + approved: bool + } + + + //////////////////////////////// + // Constructor - initialized on deployment + //////////////////////////////// + #[constructor] + fn constructor(ref self: ContractState, _name: felt252, _symbol: felt252) { + self.name.write(_name); + self.symbol.write(_symbol); + } + + #[abi(embed_v0)] + impl IERC721Impl of super::IERC721 { + //////////////////////////////// + // get_name function returns token name + //////////////////////////////// + fn get_name(self: @ContractState) -> felt252 { + self.name.read() + } + + //////////////////////////////// + // get_symbol function returns token symbol + //////////////////////////////// + fn get_symbol(self: @ContractState) -> felt252 { + self.symbol.read() + } + + //////////////////////////////// + // token_uri returns the token uri + //////////////////////////////// + fn get_token_uri(self: @ContractState, token_id: u256) -> felt252 { + assert(self._exists(token_id), 'ERC721: invalid token ID'); + self.token_uri.read(token_id) + } + + //////////////////////////////// + // balance_of function returns token balance + //////////////////////////////// + fn balance_of(self: @ContractState, account: ContractAddress) -> u256 { + assert(account.is_non_zero(), 'ERC721: address zero'); + self.balances.read(account) + } + + //////////////////////////////// + // owner_of function returns owner of token_id + //////////////////////////////// + fn owner_of(self: @ContractState, token_id: u256) -> ContractAddress { + let owner = self.owners.read(token_id); + owner + } + + //////////////////////////////// + // get_approved function returns approved address for a token + //////////////////////////////// + fn get_approved(self: @ContractState, token_id: u256) -> ContractAddress { + assert(self._exists(token_id), 'ERC721: invalid token ID'); + self.token_approvals.read(token_id) + } + + //////////////////////////////// + // is_approved_for_all function returns approved operator for a token + //////////////////////////////// + fn is_approved_for_all( + self: @ContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool { + self.operator_approvals.read((owner, operator)) + } + + //////////////////////////////// + // approve function approves an address to spend a token + //////////////////////////////// + fn approve(ref self: ContractState, to: ContractAddress, token_id: u256) { + let owner = self.owner_of(token_id); + assert(to != owner, 'Approval to current owner'); + assert( + get_caller_address() == owner + || self.is_approved_for_all(owner, get_caller_address()), + 'Not token owner' + ); + self.token_approvals.write(token_id, to); + self.emit(Approval { owner: self.owner_of(token_id), to: to, token_id: token_id }); + } + + //////////////////////////////// + // set_approval_for_all function approves an operator to spend all tokens + //////////////////////////////// + fn set_approval_for_all( + ref self: ContractState, operator: ContractAddress, approved: bool + ) { + let owner = get_caller_address(); + assert(owner != operator, 'ERC721: approve to caller'); + self.operator_approvals.write((owner, operator), approved); + self.emit(ApprovalForAll { owner: owner, operator: operator, approved: approved }); + } + + //////////////////////////////// + // transfer_from function is used to transfer a token + //////////////////////////////// + fn transfer_from( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + assert( + self._is_approved_or_owner(get_caller_address(), token_id), + 'neither owner nor approved' + ); + self._transfer(from, to, token_id); + } + + fn mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + self._mint(to, token_id); + } + } + + #[generate_trait] + impl ERC721HelperImpl of ERC721HelperTrait { + //////////////////////////////// + // internal function to check if a token exists + //////////////////////////////// + fn _exists(self: @ContractState, token_id: u256) -> bool { + // check that owner of token is not zero + self.owner_of(token_id).is_non_zero() + } + + //////////////////////////////// + // _is_approved_or_owner checks if an address is an approved spender or owner + //////////////////////////////// + fn _is_approved_or_owner( + self: @ContractState, spender: ContractAddress, token_id: u256 + ) -> bool { + let owner = self.owners.read(token_id); + spender == owner + || self.is_approved_for_all(owner, spender) + || self.get_approved(token_id) == spender + } + + //////////////////////////////// + // internal function that sets the token uri + //////////////////////////////// + fn _set_token_uri(ref self: ContractState, token_id: u256, token_uri: felt252) { + assert(self._exists(token_id), 'ERC721: invalid token ID'); + self.token_uri.write(token_id, token_uri) + } + + //////////////////////////////// + // internal function that performs the transfer logic + //////////////////////////////// + fn _transfer( + ref self: ContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ) { + // check that from address is equal to owner of token + assert(from == self.owner_of(token_id), 'ERC721: Caller is not owner'); + // check that to address is not zero + assert(to.is_non_zero(), 'ERC721: transfer to 0 address'); + + // remove previously made approvals + self.token_approvals.write(token_id, Zero::zero()); + + // increase balance of to address, decrease balance of from address + self.balances.write(from, self.balances.read(from) - 1.into()); + self.balances.write(to, self.balances.read(to) + 1.into()); + + // update token_id owner + self.owners.write(token_id, to); + + // emit the Transfer event + self.emit(Transfer { from: from, to: to, token_id: token_id }); + } + + //////////////////////////////// + // _mint function mints a new token to the to address + //////////////////////////////// + fn _mint(ref self: ContractState, to: ContractAddress, token_id: u256) { + assert(to.is_non_zero(), 'TO_IS_ZERO_ADDRESS'); + + // Ensures token_id is unique + assert(!self.owner_of(token_id).is_non_zero(), 'ERC721: Token already minted'); + + // Increase receiver balance + let receiver_balance = self.balances.read(to); + self.balances.write(to, receiver_balance + 1.into()); + + // Update token_id owner + self.owners.write(token_id, to); + + // emit Transfer event + self.emit(Transfer { from: Zero::zero(), to: to, token_id: token_id }); + } + + //////////////////////////////// + // _burn function burns token from owner's account + //////////////////////////////// + fn _burn(ref self: ContractState, token_id: u256) { + let owner = self.owner_of(token_id); + + // Clear approvals + self.token_approvals.write(token_id, Zero::zero()); + + // Decrease owner balance + let owner_balance = self.balances.read(owner); + self.balances.write(owner, owner_balance - 1.into()); + + // Delete owner + self.owners.write(token_id, Zero::zero()); + // emit the Transfer event + self.emit(Transfer { from: owner, to: Zero::zero(), token_id: token_id }); + } + } +} diff --git a/listings/applications/nft_dutch_auction/src/lib.cairo b/listings/applications/nft_dutch_auction/src/lib.cairo new file mode 100644 index 00000000..33c4ff24 --- /dev/null +++ b/listings/applications/nft_dutch_auction/src/lib.cairo @@ -0,0 +1,5 @@ +pub mod nft_dutch_auction; +pub mod erc721; + +#[cfg(test)] +pub mod tests; diff --git a/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo b/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo new file mode 100644 index 00000000..2ffaf7e5 --- /dev/null +++ b/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo @@ -0,0 +1,135 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IERC20 { + fn get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_decimals(self: @TContractState) -> u8; + fn get_total_supply(self: @TContractState) -> felt252; + fn balance_of(self: @TContractState, account: ContractAddress) -> felt252; + fn allowance( + self: @TContractState, owner: ContractAddress, spender: ContractAddress + ) -> felt252; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252); + fn transfer_from( + ref self: TContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: felt252 + ); + fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252); + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252); + fn decrease_allowance( + ref self: TContractState, spender: ContractAddress, subtracted_value: felt252 + ); +} + +#[starknet::interface] +trait IERC721 { + fn get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_token_uri(self: @TContractState, token_id: u256) -> felt252; + fn balance_of(self: @TContractState, account: ContractAddress) -> u256; + fn owner_of(self: @TContractState, token_id: u256) -> ContractAddress; + fn get_approved(self: @TContractState, token_id: u256) -> ContractAddress; + fn is_approved_for_all( + self: @TContractState, owner: ContractAddress, operator: ContractAddress + ) -> bool; + fn approve(ref self: TContractState, to: ContractAddress, token_id: u256); + fn set_approval_for_all(ref self: TContractState, operator: ContractAddress, approved: bool); + fn transfer_from( + ref self: TContractState, from: ContractAddress, to: ContractAddress, token_id: u256 + ); + fn mint(ref self: TContractState, to: ContractAddress, token_id: u256); +} + +#[starknet::interface] +pub trait INFTDutchAuction { + fn buy(ref self: TContractState, token_id: u256); + fn get_price(self: @TContractState) -> u64; +} + +#[starknet::contract] +pub mod NFTDutchAuction { + use super::{IERC20Dispatcher, IERC20DispatcherTrait, IERC721Dispatcher, IERC721DispatcherTrait}; + use starknet::{ContractAddress, get_caller_address, get_contract_address, get_block_timestamp}; + + #[storage] + struct Storage { + erc20_token: ContractAddress, + erc721_token: ContractAddress, + starting_price: u64, + seller: ContractAddress, + duration: u64, + discount_rate: u64, + start_at: u64, + expires_at: u64, + purchase_count: u128, + total_supply: u128 + } + + mod Errors { + pub const AUCTION_ENDED: felt252 = 'auction has ended'; + pub const LOW_STARTING_PRICE: felt252 = 'low starting price'; + pub const INSUFFICIENT_BALANCE: felt252 = 'insufficient balance'; + } + + #[constructor] + fn constructor( + ref self: ContractState, + erc20_token: ContractAddress, + erc721_token: ContractAddress, + starting_price: u64, + seller: ContractAddress, + duration: u64, + discount_rate: u64, + total_supply: u128 + ) { + assert(starting_price >= discount_rate * duration, Errors::LOW_STARTING_PRICE); + + self.erc20_token.write(erc20_token); + self.erc721_token.write(erc721_token); + self.starting_price.write(starting_price); + self.seller.write(seller); + self.duration.write(duration); + self.discount_rate.write(discount_rate); + self.start_at.write(get_block_timestamp()); + self.expires_at.write(get_block_timestamp() + duration * 1000); + self.total_supply.write(total_supply); + } + + #[abi(embed_v0)] + impl NFTDutchAuction of super::INFTDutchAuction { + fn get_price(self: @ContractState) -> u64 { + let time_elapsed = (get_block_timestamp() - self.start_at.read()) + / 1000; // Ignore milliseconds + let discount = self.discount_rate.read() * time_elapsed; + self.starting_price.read() - discount + } + + fn buy(ref self: ContractState, token_id: u256) { + // Check duration + assert(get_block_timestamp() < self.expires_at.read(), Errors::AUCTION_ENDED); + // Check total supply + assert(self.purchase_count.read() < self.total_supply.read(), Errors::AUCTION_ENDED); + + let erc20_dispatcher = IERC20Dispatcher { contract_address: self.erc20_token.read() }; + let erc721_dispatcher = IERC721Dispatcher { + contract_address: self.erc721_token.read() + }; + + let caller = get_caller_address(); + // Get NFT price + let price: u256 = self.get_price().into(); + let buyer_balance: u256 = erc20_dispatcher.balance_of(caller).into(); + // Ensure buyer has enough token for payment + assert(buyer_balance >= price, Errors::INSUFFICIENT_BALANCE); + // Transfer payment token from buyer to seller + erc20_dispatcher.transfer_from(caller, self.seller.read(), price.try_into().unwrap()); + // Mint token to buyer's address + erc721_dispatcher.mint(caller, token_id); + // Increase purchase count + self.purchase_count.write(self.purchase_count.read() + 1); + } + } +} diff --git a/listings/applications/nft_dutch_auction/src/tests.cairo b/listings/applications/nft_dutch_auction/src/tests.cairo new file mode 100644 index 00000000..be89a25f --- /dev/null +++ b/listings/applications/nft_dutch_auction/src/tests.cairo @@ -0,0 +1,215 @@ +use core::option::OptionTrait; +use core::traits::{Into, TryInto}; +use starknet::ContractAddress; +use snforge_std::{ + BlockId, declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan, + cheat_block_timestamp +}; +use super::{ + erc721::{IERC721Dispatcher, IERC721DispatcherTrait}, + nft_dutch_auction::{INFTDutchAuctionDispatcher, INFTDutchAuctionDispatcherTrait} +}; +use erc20::token::{IERC20Dispatcher, IERC20DispatcherTrait}; + +// ERC721 token +pub const erc721_name: felt252 = 'My NFT'; +pub const erc721_symbol: felt252 = 'MNFT'; + +// ERC20 token +pub const erc20_name: felt252 = 'My Token'; +pub const erc20_symbol: felt252 = 'MTKN'; +pub const erc20_recipient: felt252 = 'admin'; +pub const erc20_decimals: u8 = 1_u8; +pub const erc20_initial_supply: u128 = 10000_u128; + +// NFT Auction +pub const starting_price: felt252 = 500; +pub const seller: felt252 = 'seller'; +pub const duration: felt252 = 60; // in seconds +pub const discount_rate: felt252 = 5; +pub const total_supply: felt252 = 2; + +fn get_contract_addresses() -> (ContractAddress, ContractAddress, ContractAddress) { + let erc721 = declare("ERC721").unwrap(); + let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; + let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); + let erc20 = declare("erc20").unwrap(); + let erc20_constructor_calldata = array![ + erc20_recipient, + erc20_name, + erc20_decimals.into(), + erc20_initial_supply.into(), + erc20_symbol + ]; + let (erc20_address, _) = erc20.deploy(@erc20_constructor_calldata).unwrap(); + let nft_auction = declare("NFTDutchAuction").unwrap(); + let nft_auction_constructor_calldata = array![ + erc20_address.into(), + erc721_address.into(), + starting_price, + seller, + duration, + discount_rate, + total_supply + ]; + let (nft_auction_address, _) = nft_auction.deploy(@nft_auction_constructor_calldata).unwrap(); + (erc721_address, erc20_address, nft_auction_address) +} + +#[test] +fn test_buy() { + let (erc721_address, erc20_address, nft_auction_address) = get_contract_addresses(); + let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); + let seller: ContractAddress = 'seller'.try_into().unwrap(); + let buyer: ContractAddress = 'buyer'.try_into().unwrap(); + + // Transfer erc20 tokens to buyer + assert_eq!(erc20_dispatcher.balance_of(buyer), 0.into()); + cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); + let transfer_amt = 5000; + erc20_dispatcher.transfer(buyer, transfer_amt.into()); + assert_eq!(erc20_dispatcher.balance_of(buyer), transfer_amt.into()); + + // Buy token + cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(3)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(2)); + + let nft_id_1 = 1; + let seller_bal_before_buy = erc20_dispatcher.balance_of(seller); + let buyer_bal_before_buy = erc20_dispatcher.balance_of(buyer); + let nft_price = nft_auction_dispatcher.get_price().into(); + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + + nft_auction_dispatcher.buy(nft_id_1); + + let seller_bal_after_buy = erc20_dispatcher.balance_of(seller); + let buyer_bal_after_buy = erc20_dispatcher.balance_of(buyer); + + assert_eq!(seller_bal_after_buy, seller_bal_before_buy + nft_price); + assert_eq!(buyer_bal_after_buy, buyer_bal_before_buy - nft_price); + assert_eq!(erc721_dispatcher.owner_of(nft_id_1), buyer); + + // Forward block timestamp in order for a reduced nft price + let forward_blocktime_by = 4000; // milliseconds + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + // Buy token again after some time + let nft_id_2 = 2; + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + + assert_ne!(erc721_dispatcher.owner_of(nft_id_2), buyer); + nft_auction_dispatcher.buy(nft_id_2); + assert_eq!(erc721_dispatcher.owner_of(nft_id_2), buyer); +} + +#[test] +#[should_panic(expected: 'auction has ended')] +fn test_buy_should_panic_when_total_supply_reached() { + let (_, erc20_address, nft_auction_address) = get_contract_addresses(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); + let buyer: ContractAddress = 'buyer'.try_into().unwrap(); + + // Transfer erc20 tokens to buyer + cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); + let transfer_amt = 5000; + erc20_dispatcher.transfer(buyer, transfer_amt.into()); + + // Buy token + cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(4)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(3)); + + let nft_id_1 = 1; + let nft_price = nft_auction_dispatcher.get_price().into(); + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_1); + + // Forward block timestamp in order for a reduced nft price + let forward_blocktime_by = 4000; // 4 seconds (in milliseconds) + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + // Buy token again after some time + let nft_id_2 = 2; + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_2); + + // Buy token again after the total supply has reached + let nft_id_3 = 3; + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_3); +} + +#[test] +#[should_panic(expected: 'auction has ended')] +fn test_buy_should_panic_when_duration_ended() { + let (_, erc20_address, nft_auction_address) = get_contract_addresses(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); + let buyer: ContractAddress = 'buyer'.try_into().unwrap(); + + // Transfer erc20 tokens to buyer + cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); + let transfer_amt = 5000; + erc20_dispatcher.transfer(buyer, transfer_amt.into()); + + // Buy token + cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(4)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(3)); + + let nft_id_1 = 1; + let nft_price = nft_auction_dispatcher.get_price().into(); + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_1); + + // Forward block timestamp to a time after duration has ended + // During deployment, duration was set to 60 seconds + let forward_blocktime_by = 61000; // 61 seconds (in milliseconds) + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + // Buy token again after some time + let nft_id_2 = 2; + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_2); +} + +#[test] +fn test_price_decreases_after_some_time() { + let (_, _, nft_auction_address) = get_contract_addresses(); + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + + let nft_price_before_time_travel = nft_auction_dispatcher.get_price(); + + // Forward time + let forward_blocktime_by = 10000; // 10 seconds (in milliseconds) + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + let nft_price_after_time_travel = nft_auction_dispatcher.get_price(); + + println!("price: {:?}", nft_price_after_time_travel); + + assert_gt!(nft_price_before_time_travel, nft_price_after_time_travel); +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 799cb263..955beabe 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -55,6 +55,7 @@ Summary - [Upgradeable Contract](./ch01/upgradeable_contract.md) - [Defi Vault](./ch01/simple_vault.md) - [ERC20 Token](./ch01/erc20.md) +- [NFT Dutch Auction](./ch01/nft_dutch_auction.md) - [Constant Product AMM](./ch01/constant-product-amm.md) - [TimeLock](./ch01/timelock.md) - [Staking](./ch01/staking.md) diff --git a/src/ch01/nft_dutch_auction.md b/src/ch01/nft_dutch_auction.md new file mode 100644 index 00000000..765b3736 --- /dev/null +++ b/src/ch01/nft_dutch_auction.md @@ -0,0 +1,14 @@ +# NFT Dutch Auction + +This is the Cairo adaptation (with some modifications) of the [Solidity by example NFT Dutch Auction](https://solidity-by-example.org/app/dutch-auction/). + +Here's how it works: +- The seller of the NFT deploys this contract with a startingPrice. +- The auction lasts for a specified duration. +- The price decreases over time. +- Participants can purchase NFTs at any time as long as the totalSupply has not been reached. +- The auction ends when either the totalSupply is reached or the duration has elapsed. + +```rust +{{#include ../../listings/applications/nft_auction/src/nft_auction.cairo}} +``` \ No newline at end of file From 3fbfb60070e73b633af57ba4eece89321421a452 Mon Sep 17 00:00:00 2001 From: Wolf <81079370+raizo07@users.noreply.github.com> Date: Sun, 9 Jun 2024 07:49:46 +0100 Subject: [PATCH 18/36] fix: chapter-related folder names (#216) * fix: chapter-related folder names * updated all references --- po/es.po | 772 +++++++++--------- po/messages.pot | 750 ++++++++--------- po/zh-cn.po | 618 +++++++------- src/SUMMARY.md | 107 ++- .../hash-solidity-compatible.md | 0 src/{ch02 => advanced-concepts}/hashing.md | 0 .../library_calls.md | 0 src/{ch02 => advanced-concepts}/list.md | 0 .../optimisations/optimisations.md | 0 .../optimisations/store_using_packing.md | 0 src/{ch02 => advanced-concepts}/plugins.md | 0 .../signature_verification.md | 0 .../storing_arrays.md | 2 +- .../struct-mapping-key.md | 0 .../write_to_any_slot.md | 0 .../constant-product-amm.md | 0 src/{ch01 => applications}/erc20.md | 0 .../nft_dutch_auction.md | 0 .../signature_verification.md | 0 src/{ch01 => applications}/simple_vault.md | 0 src/{ch01 => applications}/staking.md | 0 src/{ch01 => applications}/timelock.md | 0 .../upgradeable_contract.md | 0 .../basics/bytearrays-strings.md | 0 .../basics/constructor.md | 0 .../basics/counter.md | 0 .../basics/custom-types-in-entrypoints.md | 0 .../basics/documentation.md | 0 .../basics/errors.md | 0 .../basics/events.md | 0 .../basics/introduction.md | 0 .../basics/mappings.md | 0 .../basics/storage.md | 0 .../basics/storing-custom-types.md | 0 .../basics/syscalls.md | 2 +- .../basics/variables.md | 0 .../basics/visibility-mutability.md | 0 .../cairo_cheatsheet/arrays.md | 0 .../cairo_cheatsheet/cairo_cheatsheet.md | 0 .../cairo_cheatsheet/enums.md | 0 .../cairo_cheatsheet/felt.md | 0 .../cairo_cheatsheet/if_let.md | 0 .../cairo_cheatsheet/loop.md | 0 .../cairo_cheatsheet/mapping.md | 0 .../cairo_cheatsheet/match.md | 0 .../cairo_cheatsheet/struct.md | 0 .../cairo_cheatsheet/tuples.md | 0 .../cairo_cheatsheet/type_casting.md | 0 .../cairo_cheatsheet/while.md | 0 .../cairo_cheatsheet/while_let.md | 0 src/{ch00 => getting-started}/env_setup.md | 0 .../interacting/calling_other_contracts.md | 0 .../interacting/factory.md | 0 .../interacting/interacting.md | 0 .../interacting/interfaces-traits.md | 0 .../testing/contract-testing.md | 0 56 files changed, 1125 insertions(+), 1126 deletions(-) rename src/{ch02 => advanced-concepts}/hash-solidity-compatible.md (100%) rename src/{ch02 => advanced-concepts}/hashing.md (100%) rename src/{ch02 => advanced-concepts}/library_calls.md (100%) rename src/{ch02 => advanced-concepts}/list.md (100%) rename src/{ch02 => advanced-concepts}/optimisations/optimisations.md (100%) rename src/{ch02 => advanced-concepts}/optimisations/store_using_packing.md (100%) rename src/{ch02 => advanced-concepts}/plugins.md (100%) rename src/{ch02 => advanced-concepts}/signature_verification.md (100%) rename src/{ch02 => advanced-concepts}/storing_arrays.md (87%) rename src/{ch02 => advanced-concepts}/struct-mapping-key.md (100%) rename src/{ch02 => advanced-concepts}/write_to_any_slot.md (100%) rename src/{ch01 => applications}/constant-product-amm.md (100%) rename src/{ch01 => applications}/erc20.md (100%) rename src/{ch01 => applications}/nft_dutch_auction.md (100%) rename src/{ch01 => applications}/signature_verification.md (100%) rename src/{ch01 => applications}/simple_vault.md (100%) rename src/{ch01 => applications}/staking.md (100%) rename src/{ch01 => applications}/timelock.md (100%) rename src/{ch01 => applications}/upgradeable_contract.md (100%) rename src/{ch00 => getting-started}/basics/bytearrays-strings.md (100%) rename src/{ch00 => getting-started}/basics/constructor.md (100%) rename src/{ch00 => getting-started}/basics/counter.md (100%) rename src/{ch00 => getting-started}/basics/custom-types-in-entrypoints.md (100%) rename src/{ch00 => getting-started}/basics/documentation.md (100%) rename src/{ch00 => getting-started}/basics/errors.md (100%) rename src/{ch00 => getting-started}/basics/events.md (100%) rename src/{ch00 => getting-started}/basics/introduction.md (100%) rename src/{ch00 => getting-started}/basics/mappings.md (100%) rename src/{ch00 => getting-started}/basics/storage.md (100%) rename src/{ch00 => getting-started}/basics/storing-custom-types.md (100%) rename src/{ch00 => getting-started}/basics/syscalls.md (99%) rename src/{ch00 => getting-started}/basics/variables.md (100%) rename src/{ch00 => getting-started}/basics/visibility-mutability.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/arrays.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/cairo_cheatsheet.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/enums.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/felt.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/if_let.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/loop.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/mapping.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/match.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/struct.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/tuples.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/type_casting.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/while.md (100%) rename src/{ch00 => getting-started}/cairo_cheatsheet/while_let.md (100%) rename src/{ch00 => getting-started}/env_setup.md (100%) rename src/{ch00 => getting-started}/interacting/calling_other_contracts.md (100%) rename src/{ch00 => getting-started}/interacting/factory.md (100%) rename src/{ch00 => getting-started}/interacting/interacting.md (100%) rename src/{ch00 => getting-started}/interacting/interfaces-traits.md (100%) rename src/{ch00 => getting-started}/testing/contract-testing.md (100%) diff --git a/po/es.po b/po/es.po index 3adf59e9..baa154ae 100644 --- a/po/es.po +++ b/po/es.po @@ -317,30 +317,30 @@ msgstr "" "Para obtener más recursos, consulte [Awesome Starknet](https://github.com/" "keep-starknet-strange/awesome-starknet)." -#: src/starknet-by-example.md:31 src/ch00/basics/storage.md:34 -#: src/ch00/basics/constructor.md:27 src/ch00/basics/variables.md:126 -#: src/ch00/basics/visibility-mutability.md:75 src/ch00/basics/counter.md:56 -#: src/ch00/basics/errors.md:139 src/ch00/basics/storing-custom-types.md:39 -#: src/ch00/basics/custom-types-in-entrypoints.md:39 -#: src/ch00/interacting/interfaces-traits.md:129 -#: src/ch00/interacting/calling_other_contracts.md:69 -#: src/ch00/cairo_cheatsheet/felt.md:15 src/ch00/cairo_cheatsheet/mapping.md:58 -#: src/ch00/cairo_cheatsheet/arrays.md:42 src/ch00/cairo_cheatsheet/loop.md:23 -#: src/ch00/cairo_cheatsheet/match.md:59 src/ch00/cairo_cheatsheet/tuples.md:18 -#: src/ch00/cairo_cheatsheet/struct.md:15 -#: src/ch00/cairo_cheatsheet/type_casting.md:33 src/ch01/simple_vault.md:114 -#: src/ch01/erc20.md:228 src/ch01/constant-product-amm.md:275 -#: src/ch02/write_to_any_slot.md:58 src/ch02/storing_arrays.md:105 -#: src/ch02/struct-mapping-key.md:46 src/ch02/hash-solidity-compatible.md:46 -#: src/ch02/optimisations/store_using_packing.md:90 src/ch02/list.md:139 +#: src/starknet-by-example.md:31 src/getting-started/basics/storage.md:34 +#: src/getting-started/basics/constructor.md:27 src/getting-started/basics/variables.md:126 +#: src/getting-started/basics/visibility-mutability.md:75 src/getting-started/basics/counter.md:56 +#: src/getting-started/basics/errors.md:139 src/getting-started/basics/storing-custom-types.md:39 +#: src/getting-started/basics/custom-types-in-entrypoints.md:39 +#: src/getting-started/interacting/interfaces-traits.md:129 +#: src/getting-started/interacting/calling_other_contracts.md:69 +#: src/getting-started/cairo_cheatsheet/felt.md:15 src/getting-started/cairo_cheatsheet/mapping.md:58 +#: src/getting-started/cairo_cheatsheet/arrays.md:42 src/getting-started/cairo_cheatsheet/loop.md:23 +#: src/getting-started/cairo_cheatsheet/match.md:59 src/getting-started/cairo_cheatsheet/tuples.md:18 +#: src/getting-started/cairo_cheatsheet/struct.md:15 +#: src/getting-started/cairo_cheatsheet/type_casting.md:33 src/applications/simple_vault.md:114 +#: src/applications/erc20.md:228 src/applications/constant-product-amm.md:275 +#: src/advanced-concepts/write_to_any_slot.md:58 src/advanced-concepts/storing_arrays.md:105 +#: src/advanced-concepts/struct-mapping-key.md:46 src/advanced-concepts/hash-solidity-compatible.md:46 +#: src/advanced-concepts/optimisations/store_using_packing.md:90 src/advanced-concepts/list.md:139 msgid "
Last change: 2023-12-07
" msgstr "
Último cambio: 2023-12-07
" -#: src/ch00/basics/introduction.md:1 +#: src/getting-started/basics/introduction.md:1 msgid "# Basics of Smart Contracts in Cairo" msgstr "# Conceptos básicos de los Smart Contracts en Cairo" -#: src/ch00/basics/introduction.md:3 +#: src/getting-started/basics/introduction.md:3 msgid "" "The following chapters will introduce you to Starknet smart contracts and " "how to write them in Cairo." @@ -348,19 +348,19 @@ msgstr "" "Los siguientes capítulos le presentarán los smart contracts de Starknet y " "cómo escribirlos en Cairo." -#: src/ch00/basics/introduction.md:5 src/ch02/optimisations/optimisations.md:5 +#: src/getting-started/basics/introduction.md:5 src/advanced-concepts/optimisations/optimisations.md:5 msgid "
Last change: 2023-10-12
" msgstr "
Último cambio: 2023-10-12
" -#: src/ch00/basics/storage.md:1 +#: src/getting-started/basics/storage.md:1 msgid "# Storage" msgstr "# Almacenamiento" -#: src/ch00/basics/storage.md:3 +#: src/getting-started/basics/storage.md:3 msgid "Here's the most minimal contract you can write in Cairo:" msgstr "Este es el contrato mínimo que puedes redactar en Cairo:" -#: src/ch00/basics/storage.md:5 +#: src/getting-started/basics/storage.md:5 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -378,7 +378,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/storage.md:13 +#: src/getting-started/basics/storage.md:13 msgid "" "Storage is a struct annoted with `#[storage]`. Every contract must have one " "and only one storage.\n" @@ -390,7 +390,7 @@ msgstr "" "cada key se asignará a una dirección de almacenamiento del espacio de " "almacenamiento del contrato." -#: src/ch00/basics/storage.md:16 +#: src/getting-started/basics/storage.md:16 msgid "" "You can define [storage variables](./variables.md#storage-variables) in your " "contract, and then use them to store and retrieve data." @@ -398,7 +398,7 @@ msgstr "" "Puede definir [variables de storage](./variables.md#storage-variables) en su " "contrato y luego usarlas para almacenar y recuperar datos." -#: src/ch00/basics/storage.md:17 +#: src/getting-started/basics/storage.md:17 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -424,7 +424,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/storage.md:29 +#: src/getting-started/basics/storage.md:29 msgid "" "> Actually these two contracts have the same underlying Sierra program.\n" "> From the compiler's perspective, the storage variables don't exist until " @@ -435,18 +435,18 @@ msgstr "" "> Desde la perspectiva del compilador, las variables de almacenamiento no " "existen hasta que se utilizan." -#: src/ch00/basics/storage.md:32 +#: src/getting-started/basics/storage.md:32 msgid "" "You can also read about [storing custom types](./storing-custom-types.md)" msgstr "" "También puede leer sobre [almacenamiento de tipos personalizados](./storing-" "custom-types.md)" -#: src/ch00/basics/constructor.md:1 +#: src/getting-started/basics/constructor.md:1 msgid "# Constructor" msgstr "# Constructor" -#: src/ch00/basics/constructor.md:3 +#: src/getting-started/basics/constructor.md:3 msgid "" "Constructors are a special type of function that runs only once when " "deploying a contract, and can be used to initialize the state of the " @@ -460,7 +460,7 @@ msgstr "" "constructora debe estar anotada con el atributo `#[constructor]`. Además, " "una buena práctica consiste en denominar a esa función `constructor`." -#: src/ch00/basics/constructor.md:5 +#: src/getting-started/basics/constructor.md:5 msgid "" "Here's a simple example that demonstrates how to initialize the state of a " "contract on deployment by defining logic inside a constructor." @@ -469,7 +469,7 @@ msgstr "" "el estado de un contrato durante la implementación definiendo la lógica " "dentro de un constructor." -#: src/ch00/basics/constructor.md:7 +#: src/getting-started/basics/constructor.md:7 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -511,7 +511,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/constructor.md:25 +#: src/getting-started/basics/constructor.md:25 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x017fd6558e67451dA583d123D77F4e2651E91502D08F8F8432355293b11e1f8F) " @@ -525,15 +525,15 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/constructor/src/constructor.cairo)." -#: src/ch00/basics/variables.md:1 +#: src/getting-started/basics/variables.md:1 msgid "# Variables" msgstr "# Variables" -#: src/ch00/basics/variables.md:3 +#: src/getting-started/basics/variables.md:3 msgid "There are 3 types of variables in Cairo contracts:" msgstr "Hay 3 tipos de variables en los contratos de Cairo:" -#: src/ch00/basics/variables.md:5 +#: src/getting-started/basics/variables.md:5 msgid "" "- Local\n" " - declared inside a function\n" @@ -556,11 +556,11 @@ msgstr "" " - Se accede desde cualquier lugar, incluso dentro de las funciones de la " "biblioteca" -#: src/ch00/basics/variables.md:15 +#: src/getting-started/basics/variables.md:15 msgid "## Local Variables" msgstr "## Variables Locales" -#: src/ch00/basics/variables.md:17 +#: src/getting-started/basics/variables.md:17 msgid "" "Local variables are used and accessed within the scope of a specific " "function or block of code. They are temporary and exist only for the " @@ -570,7 +570,7 @@ msgstr "" "una función o bloque de código específico. Son temporales y existen solo " "mientras dure esa función en particular o la ejecución del bloque." -#: src/ch00/basics/variables.md:19 +#: src/getting-started/basics/variables.md:19 msgid "" "Local variables are stored in memory and are not stored on the blockchain. " "This means they cannot be accessed from one execution to another. Local " @@ -584,11 +584,11 @@ msgstr "" "son relevantes sólo dentro de un contexto específico. También hacen que el " "código sea más legible al dar nombres a los valores intermedios." -#: src/ch00/basics/variables.md:21 +#: src/getting-started/basics/variables.md:21 msgid "Here's a simple example of a contract with only local variables:" msgstr "Aquí hay un ejemplo simple de un contrato con solo variables locales:" -#: src/ch00/basics/variables.md:23 +#: src/getting-started/basics/variables.md:23 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -652,7 +652,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/variables.md:50 +#: src/getting-started/basics/variables.md:50 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x015B3a10F9689BeD741Ca3C210017BC097122CeF76f3cAA191A20ff8b9b56b96) " @@ -664,13 +664,13 @@ msgstr "" "contract/0x015B3a10F9689BeD741Ca3C210017BC097122CeF76f3cAA191A20ff8b9b56b96) " "o juegue con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/variables/src/local_variables.cairo)." +"blob/main/listings/getting-started-getting-started/variables/src/local_variables.cairo)." -#: src/ch00/basics/variables.md:52 +#: src/getting-started/basics/variables.md:52 msgid "## Storage Variables" msgstr "## Variables de Storage" -#: src/ch00/basics/variables.md:54 +#: src/getting-started/basics/variables.md:54 msgid "" "Storage variables are persistent data stored on the blockchain. They can be " "accessed from one execution to another, allowing the contract to remember " @@ -681,7 +681,7 @@ msgstr "" "permite que el contrato recuerde y actualice la información a lo largo del " "tiempo." -#: src/ch00/basics/variables.md:56 +#: src/getting-started/basics/variables.md:56 msgid "" "To write or update a storage variable, you need to interact with the " "contract through an external entrypoint by sending a transaction." @@ -690,7 +690,7 @@ msgstr "" "contrato a través de un punto de entrada externo mediante el envío de una " "transacción." -#: src/ch00/basics/variables.md:58 +#: src/getting-started/basics/variables.md:58 msgid "" "On the other hand, you can read state variables, for free, without any " "transaction, simply by interacting with a node." @@ -698,13 +698,13 @@ msgstr "" "Por otro lado, puedes leer variables de estado, de forma gratuita, sin " "ninguna transacción, simplemente interactuando con un nodo." -#: src/ch00/basics/variables.md:60 +#: src/getting-started/basics/variables.md:60 msgid "Here's a simple example of a contract with one storage variable:" msgstr "" "A continuación se muestra un ejemplo sencillo de un contrato con una " "variable de almacenamiento:" -#: src/ch00/basics/variables.md:62 +#: src/getting-started/basics/variables.md:62 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -772,7 +772,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/variables.md:92 +#: src/getting-started/basics/variables.md:92 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x06eA827B32875483709b785A7F9e846a52776Cd8D42C3fE696218c2624b0DCCa) " @@ -784,14 +784,14 @@ msgstr "" "contract/0x06eA827B32875483709b785A7F9e846a52776Cd8D42C3fE696218c2624b0DCCa) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/variables/src/storage_variables." +"blob/main/listings/getting-started-getting-started/variables/src/storage_variables." "cairo)." -#: src/ch00/basics/variables.md:94 +#: src/getting-started/basics/variables.md:94 msgid "## Global Variables" msgstr "## Variables Globales" -#: src/ch00/basics/variables.md:96 +#: src/getting-started/basics/variables.md:96 msgid "" "Global variables are predefined variables that provide information about the " "blockchain and the current execution environment. They can be accessed at " @@ -801,7 +801,7 @@ msgstr "" "información sobre la blockchain y el entorno de ejecución actual. ¡Se puede " "acceder a ellos en cualquier momento y desde cualquier lugar!" -#: src/ch00/basics/variables.md:98 +#: src/getting-started/basics/variables.md:98 msgid "" "In Starknet, you can access global variables by using specific functions " "contained in the starknet core libraries." @@ -809,7 +809,7 @@ msgstr "" "En Starknet, puede acceder a variables globales utilizando funciones " "específicas contenidas en las bibliotecas principales de Starknet." -#: src/ch00/basics/variables.md:100 +#: src/getting-started/basics/variables.md:100 msgid "" "For example, the `get_caller_address` function returns the address of the " "caller of the current transaction, and the `get_contract_address` function " @@ -819,7 +819,7 @@ msgstr "" "persona que llama de la transacción actual, y la función " "`get_contract_address` devuelve la dirección del contrato actual." -#: src/ch00/basics/variables.md:102 +#: src/getting-started/basics/variables.md:102 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -871,7 +871,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/variables.md:125 +#: src/getting-started/basics/variables.md:125 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x05bD2F3943bd4e030f85678b55b2EC2C1be939e32388530FB20ED967B3Be433F) " @@ -885,19 +885,19 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/variables/src/global_variables.cairo)." -#: src/ch00/basics/visibility-mutability.md:1 +#: src/getting-started/basics/visibility-mutability.md:1 msgid "# Visibility and Mutability" msgstr "# Visibilidad y Mutabilidad" -#: src/ch00/basics/visibility-mutability.md:3 +#: src/getting-started/basics/visibility-mutability.md:3 msgid "## Visibility" msgstr "## Visibilidad" -#: src/ch00/basics/visibility-mutability.md:5 +#: src/getting-started/basics/visibility-mutability.md:5 msgid "There are two types of functions in Starknet contracts:" msgstr "Hay dos tipos de funciones en los contratos Starknet:" -#: src/ch00/basics/visibility-mutability.md:7 +#: src/getting-started/basics/visibility-mutability.md:7 msgid "" "- Functions that are accessible externally and can be called by anyone.\n" "- Functions that are only accessible internally and can only be called by " @@ -908,7 +908,7 @@ msgstr "" "- Funciones a las que solo se puede acceder internamente y que solo pueden " "ser invocadas por otras funciones del contrato." -#: src/ch00/basics/visibility-mutability.md:10 +#: src/getting-started/basics/visibility-mutability.md:10 msgid "" "These functions are also typically divided into two different " "implementations blocks. The first `impl` block for externally accessible " @@ -927,11 +927,11 @@ msgstr "" "que significa que todas las funciones dentro de este bloque son privadas de " "forma predeterminada." -#: src/ch00/basics/visibility-mutability.md:12 +#: src/getting-started/basics/visibility-mutability.md:12 msgid "## State Mutability" msgstr "## Mutabilidad del Estado" -#: src/ch00/basics/visibility-mutability.md:14 +#: src/getting-started/basics/visibility-mutability.md:14 msgid "" "Regardless of whether a function is internal or external, it can either " "modify the contract's state or not. When we declare functions that interact " @@ -947,7 +947,7 @@ msgstr "" "primer parámetro de la función. Esto se puede hacer de dos maneras " "diferentes:" -#: src/ch00/basics/visibility-mutability.md:17 +#: src/getting-started/basics/visibility-mutability.md:17 msgid "" "- If we want our function to be able to mutate the state of the contract, we " "pass it by reference like this: `ref self: ContractState`.\n" @@ -959,7 +959,7 @@ msgstr "" "- Si queremos que nuestra función sea de solo lectura y no mute el estado " "del contrato, la pasamos por instantánea como esta: `self: @ContractState`." -#: src/ch00/basics/visibility-mutability.md:20 +#: src/getting-started/basics/visibility-mutability.md:20 msgid "" "Read-only functions, also called view functions, can be directly called " "without making a transaction. You can interact with them directly through a " @@ -974,7 +974,7 @@ msgstr "" "que modifican el estado del contrato, por otro lado, solo se pueden llamar " "realizando una transacción." -#: src/ch00/basics/visibility-mutability.md:23 +#: src/getting-started/basics/visibility-mutability.md:23 msgid "" "Internal functions can't be called externally, but the same principle " "applies regarding state mutability." @@ -982,12 +982,12 @@ msgstr "" "Las funciones internas no se pueden llamar externamente, pero se aplica el " "mismo principio con respecto a la mutabilidad de estado." -#: src/ch00/basics/visibility-mutability.md:25 +#: src/getting-started/basics/visibility-mutability.md:25 msgid "Let's take a look at a simple example contract to see these in action:" msgstr "" "Echemos un vistazo a un contrato de ejemplo simple para verlos en acción:" -#: src/ch00/basics/visibility-mutability.md:27 +#: src/getting-started/basics/visibility-mutability.md:27 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1105,7 +1105,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/visibility-mutability.md:73 +#: src/getting-started/basics/visibility-mutability.md:73 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x0071dE3093AB58053b0292C225aa0eED40293e7694A0042685FF6D813d39889F) " @@ -1117,21 +1117,21 @@ msgstr "" "contract/0x0071dE3093AB58053b0292C225aa0eED40293e7694A0042685FF6D813d39889F) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/visibility/src/visibility.cairo)." +"blob/main/listings/getting-started-getting-started/visibility/src/visibility.cairo)." -#: src/ch00/basics/counter.md:1 +#: src/getting-started/basics/counter.md:1 msgid "# Simple Counter" msgstr "# Counter Sencillo" -#: src/ch00/basics/counter.md:3 +#: src/getting-started/basics/counter.md:3 msgid "This is a simple counter contract." msgstr "Este es un contrato de un simple counter." -#: src/ch00/basics/counter.md:5 +#: src/getting-started/basics/counter.md:5 msgid "Here's how it works:" msgstr "Así es como trabaja:" -#: src/ch00/basics/counter.md:7 +#: src/getting-started/basics/counter.md:7 msgid "" "- The contract has a state variable called 'counter' that is initialized to " "0.\n" @@ -1149,7 +1149,7 @@ msgstr "" "- Cuando un usuario llama 'decrement', el contrato disminuye el contador en " "1." -#: src/ch00/basics/counter.md:13 +#: src/getting-started/basics/counter.md:13 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1235,7 +1235,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/counter.md:54 +#: src/getting-started/basics/counter.md:54 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x01664a69Fe701a1df7Bb0ae4A353792d0cf4E27146ee860075cbf6108b1D5718) " @@ -1247,13 +1247,13 @@ msgstr "" "contract/0x01664a69Fe701a1df7Bb0ae4A353792d0cf4E27146ee860075cbf6108b1D5718) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/counter/src/contracts.cairo)." +"blob/main/listings/getting-started-getting-started/counter/src/contracts.cairo)." -#: src/ch00/basics/mappings.md:1 +#: src/getting-started/basics/mappings.md:1 msgid "# Mappings" msgstr "# Mapas" -#: src/ch00/basics/mappings.md:3 +#: src/getting-started/basics/mappings.md:3 msgid "" "Maps are a key-value data structure used to store data within a smart " "contract. In Cairo they are implemented using the `LegacyMap` type. It's " @@ -1266,7 +1266,7 @@ msgstr "" "`LegacyMap` solo se puede usar dentro de la estructura `Storage` de un " "contrato y no se puede usar en ningún otro lugar." -#: src/ch00/basics/mappings.md:5 +#: src/getting-started/basics/mappings.md:5 msgid "" "Here we demonstrate how to use the `LegacyMap` type within a Cairo contract, " "to map between a key of type `ContractAddress` and value of type `felt252`. " @@ -1283,11 +1283,11 @@ msgstr "" "una clave determinada llamando al método `read()` y pasando la clave " "relevante." -#: src/ch00/basics/mappings.md:7 +#: src/getting-started/basics/mappings.md:7 msgid "Some additional notes:" msgstr "Algunas notas adicionales:" -#: src/ch00/basics/mappings.md:9 +#: src/getting-started/basics/mappings.md:9 msgid "" "- More complex key-value mappings are possible, for example we could use " "`LegacyMap::<(ContractAddress, ContractAddress), felt252>` to create an " @@ -1312,7 +1312,7 @@ msgstr "" "architecture_and_concepts/Smart_Contracts/contract-storage/" "#storage_variables)." -#: src/ch00/basics/mappings.md:13 +#: src/getting-started/basics/mappings.md:13 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -1382,7 +1382,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/mappings.md:44 +#: src/getting-started/basics/mappings.md:44 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x06214AB4c23Cc545bf2221D465eB83aFb7412779AD498BD48a724B3F645E3505) " @@ -1394,18 +1394,18 @@ msgstr "" "contract/0x06214AB4c23Cc545bf2221D465eB83aFb7412779AD498BD48a724B3F645E3505) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/mappings/src/mappings.cairo)." +"blob/main/listings/getting-started-getting-started/mappings/src/mappings.cairo)." -#: src/ch00/basics/mappings.md:46 src/components/how_to.md:175 +#: src/getting-started/basics/mappings.md:46 src/components/how_to.md:175 #: src/components/dependencies.md:504 src/components/collisions.md:121 msgid "
Last change: 2023-12-17
" msgstr "
Último cambio: 2023-12-17
" -#: src/ch00/basics/errors.md:1 +#: src/getting-started/basics/errors.md:1 msgid "# Errors" msgstr "# Errores" -#: src/ch00/basics/errors.md:3 +#: src/getting-started/basics/errors.md:3 msgid "" "Errors can be used to handle validation and other conditions that may occur " "during the execution of a smart contract.\n" @@ -1419,11 +1419,11 @@ msgstr "" "contract, la ejecución se detiene y se revierte cualquier cambio realizado " "durante la transacción." -#: src/ch00/basics/errors.md:6 +#: src/getting-started/basics/errors.md:6 msgid "To throw an error, use the `assert` or `panic` functions:" msgstr "Para generar un error, use las funciones `assert` o `panic`:" -#: src/ch00/basics/errors.md:8 +#: src/getting-started/basics/errors.md:8 msgid "" "- `assert` is used to validate conditions.\n" " If the check fails, an error is thrown along with a specified value, often " @@ -1447,11 +1447,11 @@ msgstr "" " (Utilice `panic_with_felt252` para poder pasar directamente un fieltro252 " "como valor de error)" -#: src/ch00/basics/errors.md:16 +#: src/getting-started/basics/errors.md:16 msgid "Here's a simple example that demonstrates the use of these functions:" msgstr "Aquí hay un ejemplo simple que demuestra el uso de estas funciones:" -#: src/ch00/basics/errors.md:18 +#: src/getting-started/basics/errors.md:18 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1511,7 +1511,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/errors.md:46 +#: src/getting-started/basics/errors.md:46 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x0022664463FF0b711CC9B549a9E87d65A0882bB1D29338C4108696B8F2216a40) " @@ -1523,13 +1523,13 @@ msgstr "" "contract/0x0022664463FF0b711CC9B549a9E87d65A0882bB1D29338C4108696B8F2216a40) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/errors/src/simple_errors.cairo)." +"blob/main/listings/getting-started-getting-started/errors/src/simple_errors.cairo)." -#: src/ch00/basics/errors.md:48 +#: src/getting-started/basics/errors.md:48 msgid "## Custom errors" msgstr "## Errores personalizados" -#: src/ch00/basics/errors.md:50 +#: src/getting-started/basics/errors.md:50 msgid "" "You can make error handling easier by defining your error codes in a " "specific module." @@ -1537,7 +1537,7 @@ msgstr "" "Puede facilitar el manejo de errores definiendo sus códigos de error en un " "módulo específico." -#: src/ch00/basics/errors.md:52 +#: src/getting-started/basics/errors.md:52 msgid "" "```rust\n" "mod Errors {\n" @@ -1609,7 +1609,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/errors.md:85 +#: src/getting-started/basics/errors.md:85 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x0501CD5da5B453a18515B5A20b8029bd7583DFE7a399ad9f79c284F7829e4A57) " @@ -1621,13 +1621,13 @@ msgstr "" "contract/0x0501CD5da5B453a18515B5A20b8029bd7583DFE7a399ad9f79c284F7829e4A57) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/errors/src/custom_errors.cairo)." +"blob/main/listings/getting-started-getting-started/errors/src/custom_errors.cairo)." -#: src/ch00/basics/errors.md:87 +#: src/getting-started/basics/errors.md:87 msgid "## Vault example" msgstr "## Ejemplo de Vault" -#: src/ch00/basics/errors.md:89 +#: src/getting-started/basics/errors.md:89 msgid "" "Here's another example that demonstrates the use of errors in a more complex " "contract:" @@ -1635,7 +1635,7 @@ msgstr "" "Aquí hay otro ejemplo que demuestra el uso de errores en un contrato más " "complejo:" -#: src/ch00/basics/errors.md:91 +#: src/getting-started/basics/errors.md:91 msgid "" "```rust\n" "mod VaultErrors {\n" @@ -1731,7 +1731,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/errors.md:137 +#: src/getting-started/basics/errors.md:137 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x020C2da26F42A28Ef54ED428eF1810FE433784b055f9bF315C5d992b1579C268) " @@ -1743,13 +1743,13 @@ msgstr "" "contract/0x020C2da26F42A28Ef54ED428eF1810FE433784b055f9bF315C5d992b1579C268) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/errors/src/vault_errors.cairo)." +"blob/main/listings/getting-started-getting-started/errors/src/vault_errors.cairo)." -#: src/ch00/basics/events.md:1 +#: src/getting-started/basics/events.md:1 msgid "# Events" msgstr "# Eventos" -#: src/ch00/basics/events.md:3 +#: src/getting-started/basics/events.md:3 msgid "" "Events are a way to emit data from a contract. All events must be defined in " "the `Event` enum, which must be annotated with the `#[event]` attribute.\n" @@ -1768,7 +1768,7 @@ msgstr "" "consultar los datos más adelante. Los datos de eventos se pueden indexar " "agregando un atributo `#[key]` a un miembro de campo." -#: src/ch00/basics/events.md:6 +#: src/getting-started/basics/events.md:6 msgid "" "Here's a simple example of a contract using events that emit an event each " "time a counter is incremented by the \"increment\" function:" @@ -1777,7 +1777,7 @@ msgstr "" "eventos que emiten un evento cada vez que la función `increment` incrementa " "un contador:" -#: src/ch00/basics/events.md:8 +#: src/getting-started/basics/events.md:8 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1905,7 +1905,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/events.md:66 +#: src/getting-started/basics/events.md:66 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x022e3B59518EA04aBb5da671ea04ecC3a154400f226d2Df38eFE146741b9E2F6) " @@ -1917,18 +1917,18 @@ msgstr "" "contract/0x022e3B59518EA04aBb5da671ea04ecC3a154400f226d2Df38eFE146741b9E2F6) " "o juega con él en [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" -"blob/main/listings/ch00-getting-started/events/src/counter.cairo)." +"blob/main/listings/getting-started-getting-started/events/src/counter.cairo)." -#: src/ch00/basics/events.md:68 src/ch00/testing/contract-testing.md:176 -#: src/ch01/upgradeable_contract.md:117 +#: src/getting-started/basics/events.md:68 src/getting-started/testing/contract-testing.md:176 +#: src/applications/upgradeable_contract.md:117 msgid "
Last change: 2023-12-21
" msgstr "
Último cambio: 2023-12-21
" -#: src/ch00/basics/syscalls.md:1 +#: src/getting-started/basics/syscalls.md:1 msgid "# Syscalls" msgstr "# Llamadas al Sistema" -#: src/ch00/basics/syscalls.md:3 +#: src/getting-started/basics/syscalls.md:3 msgid "" "At the protocol level, the Starknet Operating System (OS) is the program " "that manages the whole Starknet network." @@ -1936,7 +1936,7 @@ msgstr "" "A nivel de protocolo, el sistema operativo (SO) Starknet es el programa que " "gestiona toda la red Starknet." -#: src/ch00/basics/syscalls.md:5 +#: src/getting-started/basics/syscalls.md:5 msgid "" "Some of the OS functionalities are exposed to smart contracts through the " "use of syscalls (system calls). Syscalls can be used to get information " @@ -1949,7 +1949,7 @@ msgstr "" "red Starknet, para interactuar/implementar contratos, emitir eventos, enviar " "mensajes y realizar otras operaciones de bajo nivel." -#: src/ch00/basics/syscalls.md:7 +#: src/getting-started/basics/syscalls.md:7 msgid "" "Syscalls return a `SyscallResult` which is either `Sucess` of `Failure`, " "allowing the contract to handle errors." @@ -1957,11 +1957,11 @@ msgstr "" "Las llamadas al sistema devuelven un `SyscallResult` que es `Sucess` o " "`Failure`, lo que permite que el contrato maneje los errores." -#: src/ch00/basics/syscalls.md:9 +#: src/getting-started/basics/syscalls.md:9 msgid "Here's the available syscalls:" msgstr "Aquí están las llamadas al sistema disponibles:" -#: src/ch00/basics/syscalls.md:10 +#: src/getting-started/basics/syscalls.md:10 msgid "" "- [get_block_hash](#get_block_hash)\n" "- [get_execution_info](#get_execution_info)\n" @@ -1985,15 +1985,15 @@ msgstr "" "- [storage_read](#storage_read)\n" "- [storage_write](#storage_write)" -#: src/ch00/basics/syscalls.md:20 +#: src/getting-started/basics/syscalls.md:20 msgid "" msgstr "" -#: src/ch00/basics/syscalls.md:22 +#: src/getting-started/basics/syscalls.md:22 msgid "#### get_block_hash" msgstr "#### get_block_hash" -#: src/ch00/basics/syscalls.md:24 +#: src/getting-started/basics/syscalls.md:24 msgid "" "```rust\n" "fn get_block_hash_syscall(block_number: u64) -> SyscallResult\n" @@ -2003,19 +2003,19 @@ msgstr "" "fn get_block_hash_syscall(block_number: u64) -> SyscallResult\n" "```" -#: src/ch00/basics/syscalls.md:28 +#: src/getting-started/basics/syscalls.md:28 msgid "Get the hash of the block number `block_number`." msgstr "Obtenga el hash del número de bloque `block_number`." -#: src/ch00/basics/syscalls.md:30 +#: src/getting-started/basics/syscalls.md:30 msgid "Only within the range `[first_v0_12_0_block, current_block - 10]`." msgstr "Solo dentro del rango `[first_v0_12_0_block, current_block - 10]`." -#: src/ch00/basics/syscalls.md:32 +#: src/getting-started/basics/syscalls.md:32 msgid "#### get_execution_info" msgstr "#### get_execution_info" -#: src/ch00/basics/syscalls.md:34 +#: src/getting-started/basics/syscalls.md:34 msgid "" "```rust\n" "fn get_execution_info_syscall() -> SyscallResult>\n" "```" -#: src/ch00/basics/syscalls.md:38 +#: src/getting-started/basics/syscalls.md:38 msgid "" "Get information about the current execution context.\n" "The returned `ExecutionInfo` is defined as :" @@ -2035,7 +2035,7 @@ msgstr "" "Obtenga información sobre el contexto de ejecución actual.\n" "La `ExecutionInfo` devuelta se define como:" -#: src/ch00/basics/syscalls.md:41 +#: src/getting-started/basics/syscalls.md:41 msgid "" "```rust\n" "#[derive(Copy, Drop, Debug)]\n" @@ -2149,7 +2149,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/syscalls.md:93 +#: src/getting-started/basics/syscalls.md:93 msgid "" "`starknet::info` provides helper functions to access the `ExecutionInfo` " "fields in a more convenient way:" @@ -2157,7 +2157,7 @@ msgstr "" "`starknet::info` proporciona funciones auxiliares para acceder a los campos " "`ExecutionInfo` de una manera más conveniente:" -#: src/ch00/basics/syscalls.md:94 +#: src/getting-started/basics/syscalls.md:94 msgid "" "- `get_execution_info() -> Box`\n" "- `get_caller_address() -> ContractAddress`\n" @@ -2175,11 +2175,11 @@ msgstr "" "- `get_block_timestamp() -> u64`\n" "- `get_block_number() -> u64`" -#: src/ch00/basics/syscalls.md:102 +#: src/getting-started/basics/syscalls.md:102 msgid "#### call_contract" msgstr "#### call_contract" -#: src/ch00/basics/syscalls.md:104 +#: src/getting-started/basics/syscalls.md:104 msgid "" "```rust\n" "fn call_contract_syscall(\n" @@ -2195,7 +2195,7 @@ msgstr "" ") -> SyscallResult>\n" "```" -#: src/ch00/basics/syscalls.md:110 +#: src/getting-started/basics/syscalls.md:110 msgid "" "Call a contract at `address` with the given `entry_point_selector` and " "`calldata`.\n" @@ -2207,7 +2207,7 @@ msgstr "" "No se puede detectar una falla en esta llamada al sistema; si la llamada " "falla, se revertirá toda la transacción." -#: src/ch00/basics/syscalls.md:113 +#: src/getting-started/basics/syscalls.md:113 msgid "" "This is not the recommended way to call a contract. Instead, use the " "dispatcher generated from the contract interface as shown in the [Calling " @@ -2218,15 +2218,15 @@ msgstr "" "como se muestra en [Llamar a otros contratos] (../interacting/" "calling_other_contracts.md)." -#: src/ch00/basics/syscalls.md:115 +#: src/getting-started/basics/syscalls.md:115 msgid "" msgstr "" -#: src/ch00/basics/syscalls.md:117 +#: src/getting-started/basics/syscalls.md:117 msgid "#### deploy" msgstr "#### deploy" -#: src/ch00/basics/syscalls.md:119 +#: src/getting-started/basics/syscalls.md:119 msgid "" "```rust\n" "fn deploy_syscall(\n" @@ -2246,7 +2246,7 @@ msgstr "" ") -> SyscallResult<(ContractAddress, Span::)>\n" "```" -#: src/ch00/basics/syscalls.md:128 +#: src/getting-started/basics/syscalls.md:128 msgid "" "Deploy a new contract of the predeclared class `class_hash` with " "`calldata`.\n" @@ -2258,7 +2258,7 @@ msgstr "" "El resultado exitoso es una tupla que contiene la dirección del contrato " "implementado y el valor de retorno del constructor." -#: src/ch00/basics/syscalls.md:131 +#: src/getting-started/basics/syscalls.md:131 msgid "" "`contract_address_salt` and `deploy_from_zero` are used to compute the " "contract address." @@ -2266,7 +2266,7 @@ msgstr "" "`contract_address_salt` y `deploy_from_zero` se utilizan para calcular la " "dirección del contrato." -#: src/ch00/basics/syscalls.md:133 +#: src/getting-started/basics/syscalls.md:133 msgid "" "Example of the usage of the `deploy` syscall from the [Factory pattern](../" "interacting/factory.md):" @@ -2274,7 +2274,7 @@ msgstr "" "Ejemplo del uso de la llamada al sistema `deploy` del [Factory pattern] (../" "interacting/factory.md):" -#: src/ch00/basics/syscalls.md:135 +#: src/getting-started/basics/syscalls.md:135 msgid "" "```rust\n" "# use starknet::{ContractAddress, ClassHash};\n" @@ -2430,11 +2430,11 @@ msgstr "" "# \n" "```" -#: src/ch00/basics/syscalls.md:204 +#: src/getting-started/basics/syscalls.md:204 msgid "#### emit_event" msgstr "#### emit_event" -#: src/ch00/basics/syscalls.md:206 +#: src/getting-started/basics/syscalls.md:206 msgid "" "```rust\n" "fn emit_event_syscall(\n" @@ -2448,11 +2448,11 @@ msgstr "" ") -> SyscallResult<()>\n" "```" -#: src/ch00/basics/syscalls.md:212 +#: src/getting-started/basics/syscalls.md:212 msgid "Emit an event with the given `keys` and `data`." msgstr "Emite un evento con las `keys` y los `data` proporcionados." -#: src/ch00/basics/syscalls.md:214 +#: src/getting-started/basics/syscalls.md:214 msgid "" "Example of the usage of the `emit_event` syscall from the [Events](../basics/" "events.md) chapter:" @@ -2460,7 +2460,7 @@ msgstr "" "Ejemplo del uso de la llamada al sistema `emit_event` del capítulo [Eventos]" "(../basics/events.md):" -#: src/ch00/basics/syscalls.md:216 +#: src/getting-started/basics/syscalls.md:216 msgid "" "```rust\n" "# #[starknet::interface]\n" @@ -2592,7 +2592,7 @@ msgstr "" "# \n" "```" -#: src/ch00/basics/syscalls.md:277 +#: src/getting-started/basics/syscalls.md:277 msgid "" "\n" "" -#: src/ch00/basics/syscalls.md:289 +#: src/getting-started/basics/syscalls.md:289 msgid "#### library_call" msgstr "#### library_call" -#: src/ch00/basics/syscalls.md:291 +#: src/getting-started/basics/syscalls.md:291 msgid "" "```rust\n" "fn library_call_syscall(\n" @@ -2638,7 +2638,7 @@ msgstr "" ") -> SyscallResult>\n" "```" -#: src/ch00/basics/syscalls.md:297 +#: src/getting-started/basics/syscalls.md:297 msgid "" "Call the function `function_selector` of the class `class_hash` with " "`calldata`.\n" @@ -2650,15 +2650,15 @@ msgstr "" "Esto es análogo a una llamada de delegado en Ethereum, pero solo se llama a " "una clase." -#: src/ch00/basics/syscalls.md:300 +#: src/getting-started/basics/syscalls.md:300 msgid "" msgstr "" -#: src/ch00/basics/syscalls.md:302 +#: src/getting-started/basics/syscalls.md:302 msgid "#### send_message_to_L1" msgstr "#### send_message_to_L1" -#: src/ch00/basics/syscalls.md:304 +#: src/getting-started/basics/syscalls.md:304 msgid "" "```rust\n" "fn send_message_to_l1_syscall(\n" @@ -2672,20 +2672,20 @@ msgstr "" ") -> SyscallResult<()>\n" "```" -#: src/ch00/basics/syscalls.md:310 +#: src/getting-started/basics/syscalls.md:310 msgid "" "Send a message to the L1 contract at `to_address` with the given `payload`." msgstr "Envíe un mensaje al contrato L1 en `to_address` con la `payload` dada." -#: src/ch00/basics/syscalls.md:312 +#: src/getting-started/basics/syscalls.md:312 msgid "" msgstr "" -#: src/ch00/basics/syscalls.md:314 +#: src/getting-started/basics/syscalls.md:314 msgid "#### replace_class" msgstr "#### replace_class" -#: src/ch00/basics/syscalls.md:316 +#: src/getting-started/basics/syscalls.md:316 msgid "" "```rust\n" "fn replace_class_syscall(\n" @@ -2699,19 +2699,19 @@ msgstr "" ") -> SyscallResult<()>\n" "```" -#: src/ch00/basics/syscalls.md:322 +#: src/getting-started/basics/syscalls.md:322 msgid "Replace the class of the calling contract with the class `class_hash`." msgstr "Reemplace la clase del contrato de llamada con la clase `class_hash`." -#: src/ch00/basics/syscalls.md:324 +#: src/getting-started/basics/syscalls.md:324 msgid "" "This is used for contract upgrades. Here's an example from the [Upgradeable " -"Contract](../../ch01/upgradeable_contract.md):" +"Contract](../../applications/upgradeable_contract.md):" msgstr "" "Esto se utiliza para actualizaciones de contratos. Aquí hay un ejemplo del " -"[Contrato actualizable](../../ch01/upgradeable_contract.md):" +"[Contrato actualizable](../../applications/upgradeable_contract.md):" -#: src/ch00/basics/syscalls.md:326 +#: src/getting-started/basics/syscalls.md:326 msgid "" "```rust\n" "# use starknet::class_hash::ClassHash;\n" @@ -2809,7 +2809,7 @@ msgstr "" "# \n" "```" -#: src/ch00/basics/syscalls.md:372 +#: src/getting-started/basics/syscalls.md:372 msgid "" "The new class code will only be used for future calls to the contract.\n" "The current transaction containing the `replace_class` syscall will continue " @@ -2824,11 +2824,11 @@ msgstr "" "nuevo código de clase llamando a `call_contract` después de la llamada al " "sistema `replace_class` en la misma transacción)" -#: src/ch00/basics/syscalls.md:375 +#: src/getting-started/basics/syscalls.md:375 msgid "#### storage_read" msgstr "#### storage_read" -#: src/ch00/basics/syscalls.md:377 +#: src/getting-started/basics/syscalls.md:377 msgid "" "```rust\n" "fn storage_read_syscall(\n" @@ -2842,7 +2842,7 @@ msgstr "" ") -> SyscallResult\n" "```" -#: src/ch00/basics/syscalls.md:383 +#: src/getting-started/basics/syscalls.md:383 msgid "" "This low-level syscall is used to get the value in the storage of a specific " "key at `address` in the `address_domain`." @@ -2850,7 +2850,7 @@ msgstr "" "Esta llamada al sistema de bajo nivel se utiliza para obtener el valor en el " "almacenamiento de una clave específica en la`address` en el `address_domain`." -#: src/ch00/basics/syscalls.md:385 +#: src/getting-started/basics/syscalls.md:385 msgid "" "`address_domain` is used to distinguish between data availability modes.\n" "Currently, only mode `ONCHAIN` (`0`) is supported." @@ -2859,11 +2859,11 @@ msgstr "" "datos.\n" "Actualmente, solo se admite el modo `ONCHAIN` (`0`)." -#: src/ch00/basics/syscalls.md:388 +#: src/getting-started/basics/syscalls.md:388 msgid "#### storage_write" msgstr "#### storage_write" -#: src/ch00/basics/syscalls.md:390 +#: src/getting-started/basics/syscalls.md:390 msgid "" "```rust\n" "fn storage_write_syscall(\n" @@ -2877,7 +2877,7 @@ msgstr "" ") -> SyscallResult<()>\n" "```" -#: src/ch00/basics/syscalls.md:396 +#: src/getting-started/basics/syscalls.md:396 msgid "" "Similar to `storage_read`, this low-level syscall is used to write the value " "`value` in the storage of a specific key at `address` in the " @@ -2887,11 +2887,11 @@ msgstr "" "escribir el valor `value` en el almacenamiento de una clave específica en " "`address` en el `address_domain`." -#: src/ch00/basics/syscalls.md:398 +#: src/getting-started/basics/syscalls.md:398 msgid "## Documentation" msgstr "## Documentación" -#: src/ch00/basics/syscalls.md:400 +#: src/getting-started/basics/syscalls.md:400 msgid "" "Syscalls are defined in [`starknet::syscall`](https://github.com/starkware-" "libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/corelib/src/" @@ -2901,7 +2901,7 @@ msgstr "" "com/starkware-libs/cairo/blob/ec14a5e2c484190ff40811c973a72a53739cedb7/" "corelib/src/starknet/syscalls.cairo)" -#: src/ch00/basics/syscalls.md:402 +#: src/getting-started/basics/syscalls.md:402 msgid "" "You can also read the [official documentation page](https://docs.starknet.io/" "documentation/architecture_and_concepts/Smart_Contracts/system-calls-" @@ -2911,7 +2911,7 @@ msgstr "" "starknet.io/documentation/architecture_and_concepts/Smart_Contracts/system-" "calls-cairo1/) para obtener más detalles." -#: src/ch00/basics/syscalls.md:404 +#: src/getting-started/basics/syscalls.md:404 msgid "" "" @@ -4077,7 +4077,7 @@ msgstr "" "" -#: src/ch00/interacting/factory.md:88 +#: src/getting-started/interacting/factory.md:88 msgid "" "This factory can be used to deploy multiple instances of the `SimpleCounter` " "contract by calling the `create_counter` and `create_counter_at` functions." @@ -4086,7 +4086,7 @@ msgstr "" "contrato `SimpleCounter` llamando a las funciones `create_counter` y " "`create_counter_at`." -#: src/ch00/interacting/factory.md:90 +#: src/getting-started/interacting/factory.md:90 msgid "" "The `SimpleCounter` class hash is stored inside the factory, and can be " "upgraded with the `update_counter_class_hash` function which allows to reuse " @@ -4097,7 +4097,7 @@ msgstr "" "reutilizar el mismo contrato de fábrica cuando se actualiza el contrato " "`SimpleCounter`." -#: src/ch00/interacting/factory.md:92 +#: src/getting-started/interacting/factory.md:92 msgid "" "This minimal example lacks several useful features such as access control, " "tracking of deployed contracts, events, ..." @@ -4105,7 +4105,7 @@ msgstr "" "Este ejemplo mínimo carece de varias funciones útiles, como el control de " "acceso, el seguimiento de los contratos desplegados, los eventos, ..." -#: src/ch00/interacting/factory.md:94 +#: src/getting-started/interacting/factory.md:94 msgid "" "\n" @@ -4117,11 +4117,11 @@ msgstr "" "\n" "
Último cambio: 2023-12-21
" -#: src/ch00/testing/contract-testing.md:1 +#: src/getting-started/testing/contract-testing.md:1 msgid "# Contract Testing" msgstr "# Testing de Contrato" -#: src/ch00/testing/contract-testing.md:3 +#: src/getting-started/testing/contract-testing.md:3 msgid "" "Testing plays a crucial role in software development, especially for smart " "contracts. In this section, we'll guide you through the basics of testing a " @@ -4132,11 +4132,11 @@ msgstr "" "través de los conceptos básicos de las pruebas de un smart contracts en " "Starknet con `scarb`." -#: src/ch00/testing/contract-testing.md:5 +#: src/getting-started/testing/contract-testing.md:5 msgid "Let's start with a simple smart contract as an example:" msgstr "Empecemos con un simple contrato inteligente como ejemplo:" -#: src/ch00/testing/contract-testing.md:6 +#: src/getting-started/testing/contract-testing.md:6 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -4226,11 +4226,11 @@ msgstr "" "}\n" "```" -#: src/ch00/testing/contract-testing.md:50 +#: src/getting-started/testing/contract-testing.md:50 msgid "Now, take a look at the tests for this contract:" msgstr "Ahora, eche un vistazo a las pruebas de este contrato:" -#: src/ch00/testing/contract-testing.md:51 +#: src/getting-started/testing/contract-testing.md:51 msgid "" "```rust\n" "#[cfg(test)]\n" @@ -4408,7 +4408,7 @@ msgstr "" "}\n" "```" -#: src/ch00/testing/contract-testing.md:132 +#: src/getting-started/testing/contract-testing.md:132 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -4418,7 +4418,7 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/testing_how_to/src/lib.cairo)." -#: src/ch00/testing/contract-testing.md:134 +#: src/getting-started/testing/contract-testing.md:134 msgid "" "To define our test, we use scarb, which allows us to create a separate " "module guarded with `#[cfg(test)]`. This ensures that the test module is " @@ -4428,7 +4428,7 @@ msgstr "" "separado protegido con `#[cfg(test)]`. Esto asegura que el módulo de prueba " "sólo se compila cuando se ejecutan pruebas utilizando `scarb test`." -#: src/ch00/testing/contract-testing.md:136 +#: src/getting-started/testing/contract-testing.md:136 msgid "" "Each test is defined as a function with the `#[test]` attribute. You can " "also check if a test panics using the `#[should_panic]` attribute." @@ -4437,7 +4437,7 @@ msgstr "" "puede comprobar si una prueba entra en pánico utilizando el atributo " "`#[should_panic]`." -#: src/ch00/testing/contract-testing.md:138 +#: src/getting-started/testing/contract-testing.md:138 msgid "" "As we are in the context of a smart contract, it's essential to set up the " "gas limit. You do this by using the `#[available_gas(X)]` attribute to " @@ -4450,15 +4450,15 @@ msgstr "" "manera de asegurar que las funciones de tu contrato se mantienen por debajo " "de un cierto límite de gas!" -#: src/ch00/testing/contract-testing.md:140 +#: src/getting-started/testing/contract-testing.md:140 msgid "> Note: The term \"gas\" here refers to Sierra gas, not L1 gas" msgstr ">Nota: El término \"gas\" se refiere aquí al gas Sierra, no al gas L1" -#: src/ch00/testing/contract-testing.md:142 +#: src/getting-started/testing/contract-testing.md:142 msgid "Now, let's move on to the testing process:" msgstr "Pasemos ahora al proceso de prueba:" -#: src/ch00/testing/contract-testing.md:143 +#: src/getting-started/testing/contract-testing.md:143 msgid "" "- Use the `deploy` function logic to declare and deploy your contract.\n" "- Use `assert` to verify that the contract behaves as expected in the given " @@ -4469,7 +4469,7 @@ msgstr "" "- Utilice `assert` para verificar que el contrato se comporta como se espera " "en el contexto dado." -#: src/ch00/testing/contract-testing.md:146 +#: src/getting-started/testing/contract-testing.md:146 msgid "" "To make testing more convenient, the `testing` module of the corelib " "provides some helpful functions:" @@ -4477,7 +4477,7 @@ msgstr "" "Para que las pruebas resulten más cómodas, el módulo `testing` de corelib " "proporciona algunas funciones útiles:" -#: src/ch00/testing/contract-testing.md:147 +#: src/getting-started/testing/contract-testing.md:147 msgid "" "- `set_caller_address(address: ContractAddress)`\n" "- `set_contract_address(address: ContractAddress)`\n" @@ -4493,7 +4493,7 @@ msgstr "" "- `set_account_contract_address(address: ContractAddress)`\n" "- `set_max_fee(fee: u128)`" -#: src/ch00/testing/contract-testing.md:154 +#: src/getting-started/testing/contract-testing.md:154 msgid "" "You may also need the `info` module from the corelib, which allows you to " "access information about the current execution context (see [syscalls](../" @@ -4503,7 +4503,7 @@ msgstr "" "acceder a información sobre el contexto de ejecución actual (consulte " "[syscalls](../basics/syscalls.md)):" -#: src/ch00/testing/contract-testing.md:155 +#: src/getting-started/testing/contract-testing.md:155 msgid "" "- `get_caller_address() -> ContractAddress`\n" "- `get_contract_address() -> ContractAddress`\n" @@ -4519,7 +4519,7 @@ msgstr "" "- `get_block_timestamp() -> u64`\n" "- `get_block_number() -> u64`" -#: src/ch00/testing/contract-testing.md:163 +#: src/getting-started/testing/contract-testing.md:163 msgid "" "You can found the full list of functions in the [Starknet Corelib repo]" "(https://github.com/starkware-libs/cairo/tree/main/corelib/src/starknet).\n" @@ -4534,15 +4534,15 @@ msgstr "" "el [Libro de Cairo - Capítulo 9](https://book.cairo-lang.org/ch09-01-how-to-" "write-tests.html)." -#: src/ch00/testing/contract-testing.md:166 +#: src/getting-started/testing/contract-testing.md:166 msgid "## Starknet Foundry" msgstr "## Starknet Foundry" -#: src/ch00/testing/contract-testing.md:168 +#: src/getting-started/testing/contract-testing.md:168 msgid "" msgstr "" -#: src/ch00/testing/contract-testing.md:170 +#: src/getting-started/testing/contract-testing.md:170 msgid "" "Starknet Foundry is a powerful toolkit for developing smart contracts on " "Starknet. It offers support for testing Starknet smart contracts on top of " @@ -4552,7 +4552,7 @@ msgstr "" "de contratos inteligentes en Starknet. Ofrece soporte para probar contratos " "inteligentes Starknet sobre `scarb` con la herramienta `Forge`." -#: src/ch00/testing/contract-testing.md:172 +#: src/getting-started/testing/contract-testing.md:172 msgid "" "Testing with `snforge` is similar to the process we just described but " "simplified. Moreover, additional features are on the way, including " @@ -4564,7 +4564,7 @@ msgstr "" "cheatcodes o ejecución de pruebas en paralelo. Recomendamos encarecidamente " "explorar Starknet Foundry e incorporarlo a tus proyectos." -#: src/ch00/testing/contract-testing.md:174 +#: src/getting-started/testing/contract-testing.md:174 msgid "" "For more detailed information about testing contracts with Starknet Foundry, " "check out the [Starknet Foundry Book - Testing Contracts](https://foundry-rs." @@ -4574,11 +4574,11 @@ msgstr "" "Starknet Foundry, consulte el [Starknet Foundry Book - Testing Contracts]" "(https://foundry-rs.github.io/starknet-foundry/testing/contracts.html)." -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:1 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:1 msgid "# Cairo Cheatsheet" msgstr "# Hoja de ruta de Cairo" -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:3 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:3 msgid "" "This chapter aims to provide a quick reference for the most common Cairo " "constructs." @@ -4586,15 +4586,15 @@ msgstr "" "Este capítulo pretende ofrecer una referencia rápida para las construcciones " "más comunes de Cairo." -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:5 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:5 msgid "
Last change: 2023-10-31
" msgstr "
Último cambio: 2023-10-31
" -#: src/ch00/cairo_cheatsheet/felt.md:1 +#: src/getting-started/cairo_cheatsheet/felt.md:1 msgid "# Felt252" msgstr "# Felt252" -#: src/ch00/cairo_cheatsheet/felt.md:3 +#: src/getting-started/cairo_cheatsheet/felt.md:3 msgid "" "Felt252 is a fundamental data type in Cairo from which all other data types " "are derived.\n" @@ -4606,12 +4606,12 @@ msgstr "" "Felt252 también se puede utilizar para almacenar representaciones de cadenas " "cortas con una longitud máxima de 31 caracteres." -#: src/ch00/cairo_cheatsheet/felt.md:6 src/ch00/cairo_cheatsheet/arrays.md:20 -#: src/ch02/hash-solidity-compatible.md:5 +#: src/getting-started/cairo_cheatsheet/felt.md:6 src/getting-started/cairo_cheatsheet/arrays.md:20 +#: src/advanced-concepts/hash-solidity-compatible.md:5 msgid "For example:" msgstr "Por ejemplo:" -#: src/ch00/cairo_cheatsheet/felt.md:8 +#: src/getting-started/cairo_cheatsheet/felt.md:8 msgid "" "```rust\n" " let felt: felt252 = 100;\n" @@ -4627,18 +4627,18 @@ msgstr "" " let felt = felt + felt_as_str;\n" "```" -#: src/ch00/cairo_cheatsheet/mapping.md:1 +#: src/getting-started/cairo_cheatsheet/mapping.md:1 msgid "# Mapping" msgstr "# Mapping" -#: src/ch00/cairo_cheatsheet/mapping.md:3 +#: src/getting-started/cairo_cheatsheet/mapping.md:3 msgid "" "The ```LegacyMap``` type can be used to represent a collection of key-value." msgstr "" "El tipo ```LegacyMap``` se puede utilizar para representar una colección de " "key-value." -#: src/ch00/cairo_cheatsheet/mapping.md:5 +#: src/getting-started/cairo_cheatsheet/mapping.md:5 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -4768,11 +4768,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/arrays.md:1 +#: src/getting-started/cairo_cheatsheet/arrays.md:1 msgid "# Arrays" msgstr "# Arrays" -#: src/ch00/cairo_cheatsheet/arrays.md:3 +#: src/getting-started/cairo_cheatsheet/arrays.md:3 msgid "" "Arrays are collections of elements of the same type.\n" "The possible operations on arrays are defined with the `array::ArrayTrait` " @@ -4782,7 +4782,7 @@ msgstr "" "Las posibles operaciones sobre arrays se definen con el `array::ArrayTrait` " "de corelib:" -#: src/ch00/cairo_cheatsheet/arrays.md:6 +#: src/getting-started/cairo_cheatsheet/arrays.md:6 msgid "" "```rust\n" "trait ArrayTrait {\n" @@ -4812,7 +4812,7 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/arrays.md:22 +#: src/getting-started/cairo_cheatsheet/arrays.md:22 msgid "" "```rust\n" "fn array() -> bool {\n" @@ -4854,11 +4854,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/loop.md:1 +#: src/getting-started/cairo_cheatsheet/loop.md:1 msgid "# Loop" msgstr "# Loop" -#: src/ch00/cairo_cheatsheet/loop.md:3 +#: src/getting-started/cairo_cheatsheet/loop.md:3 msgid "" "A loop specifies a block of code that will run repetitively until a halting " "condition is encountered.\n" @@ -4868,7 +4868,7 @@ msgstr "" "hasta que se encuentre una condición de detención.\n" "Por ejemplo:" -#: src/ch00/cairo_cheatsheet/loop.md:6 +#: src/getting-started/cairo_cheatsheet/loop.md:6 msgid "" "```rust\n" " let mut arr = ArrayTrait::new();\n" @@ -4904,11 +4904,11 @@ msgstr "" " };\n" "```" -#: src/ch00/cairo_cheatsheet/match.md:1 +#: src/getting-started/cairo_cheatsheet/match.md:1 msgid "# Match" msgstr "# Match" -#: src/ch00/cairo_cheatsheet/match.md:3 +#: src/getting-started/cairo_cheatsheet/match.md:3 msgid "" "The `match` expression in Cairo allows us to control the flow of our code " "by comparing a `felt252` data type or an enum against various patterns and then " @@ -4921,7 +4921,7 @@ msgstr "" "coincide.\n" "Por ejemplo:" -#: src/ch00/cairo_cheatsheet/match.md:6 +#: src/getting-started/cairo_cheatsheet/match.md:6 msgid "" "```rust\n" "#[derive(Drop, Serde)]\n" @@ -5029,11 +5029,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/tuples.md:1 +#: src/getting-started/cairo_cheatsheet/tuples.md:1 msgid "# Tuples" msgstr "# Tuples" -#: src/ch00/cairo_cheatsheet/tuples.md:3 +#: src/getting-started/cairo_cheatsheet/tuples.md:3 msgid "" "Tuples is a data type to group a fixed number of items of potentially " "different types into a single compound structure. Unlike arrays, tuples have " @@ -5048,7 +5048,7 @@ msgstr "" "tamaño no puede cambiar.\n" "Por ejemplo:" -#: src/ch00/cairo_cheatsheet/tuples.md:6 +#: src/getting-started/cairo_cheatsheet/tuples.md:6 msgid "" "```rust\n" " let address = \"0x000\";\n" @@ -5074,11 +5074,11 @@ msgstr "" " let (address, age, active) = stored_tuple;\n" "```" -#: src/ch00/cairo_cheatsheet/struct.md:1 +#: src/getting-started/cairo_cheatsheet/struct.md:1 msgid "# Struct" msgstr "# Struct" -#: src/ch00/cairo_cheatsheet/struct.md:3 +#: src/getting-started/cairo_cheatsheet/struct.md:3 msgid "" "A struct is a data type similar to tuple. Like tuples they can be used to " "hold data of different types.\n" @@ -5088,7 +5088,7 @@ msgstr "" "tuplas, se pueden utilizar para contener datos de diferentes tipos.Por " "ejemplo:" -#: src/ch00/cairo_cheatsheet/struct.md:6 +#: src/getting-started/cairo_cheatsheet/struct.md:6 msgid "" "```rust\n" "// With Store, you can store Data's structs in the storage part of " @@ -5110,11 +5110,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/type_casting.md:1 +#: src/getting-started/cairo_cheatsheet/type_casting.md:1 msgid "# Type casting" msgstr "# Conversión de Tipos" -#: src/ch00/cairo_cheatsheet/type_casting.md:3 +#: src/getting-started/cairo_cheatsheet/type_casting.md:3 msgid "" "Cairo supports the conversion from one scalar types to another by using the " "into and try_into methods.\n" @@ -5130,7 +5130,7 @@ msgstr "" "convertir de un tipo más grande a uno más pequeño que podría no encajar.\n" "Por ejemplo:" -#: src/ch00/cairo_cheatsheet/type_casting.md:7 +#: src/getting-started/cairo_cheatsheet/type_casting.md:7 msgid "" "```rust\n" " let a_number: u32 = 15;\n" @@ -7646,15 +7646,15 @@ msgstr "" "# }\n" "```" -#: src/components/ownable.md:282 src/ch02/hashing.md:133 +#: src/components/ownable.md:282 src/advanced-concepts/hashing.md:133 msgid "
Last change: 2024-01-05
" msgstr "
Última modificación: 2024-01-05
" -#: src/ch01/upgradeable_contract.md:1 +#: src/applications/upgradeable_contract.md:1 msgid "# Upgradeable Contract" msgstr "# Contratos Actualizables" -#: src/ch01/upgradeable_contract.md:3 +#: src/applications/upgradeable_contract.md:3 msgid "" "In Starknet, contracts are divided into two parts: contract classes and " "contract\n" @@ -7669,7 +7669,7 @@ msgstr "" "programación orientados a objetos, en los que se distingue entre definición " "e implementación de objetos." -#: src/ch01/upgradeable_contract.md:8 +#: src/applications/upgradeable_contract.md:8 msgid "" "A contract class is the definition of a contract: it specifies how the " "contract\n" @@ -7684,7 +7684,7 @@ msgstr "" "nombres de puntos de entrada y todo lo que define su semántica\n" "sin ambigüedades." -#: src/ch01/upgradeable_contract.md:13 +#: src/applications/upgradeable_contract.md:13 msgid "" "To identify different contract classes, Starknet assigns a unique identifier " "to each\n" @@ -7700,7 +7700,7 @@ msgstr "" "específica. Piense en ello como una instancia de un objeto en lenguajes como " "Java." -#: src/ch01/upgradeable_contract.md:18 +#: src/applications/upgradeable_contract.md:18 msgid "" "Each class is identified by its class hash, which is analogous to a class " "name in an object-oriented programming language. A contract instance is a " @@ -7710,7 +7710,7 @@ msgstr "" "clase en un lenguaje de programación orientado a objetos. Una instancia de " "contrato es un contrato desplegado correspondiente a una clase." -#: src/ch01/upgradeable_contract.md:20 +#: src/applications/upgradeable_contract.md:20 msgid "" "You can upgrade a deployed contract to a newer version by calling the " "`replace_class_syscall` function. By using this function, you can update the " @@ -7725,7 +7725,7 @@ msgstr "" "almacenamiento del contrato, por lo que todos los datos almacenados en el " "contrato seguirán siendo los mismos." -#: src/ch01/upgradeable_contract.md:22 +#: src/applications/upgradeable_contract.md:22 msgid "" "To illustrate this concept, let's consider an example with two contracts: " "`UpgradeableContract_V0`, and `UpgradeableContract_V1`.\n" @@ -7745,7 +7745,7 @@ msgstr "" "Luego, llama al método `version` en el contrato para ver que el contrato fue " "actualizado a la versión V1." -#: src/ch01/upgradeable_contract.md:25 +#: src/applications/upgradeable_contract.md:25 msgid "" "```rust\n" "use starknet::class_hash::ClassHash;\n" @@ -7839,7 +7839,7 @@ msgstr "" "}\n" "```" -#: src/ch01/upgradeable_contract.md:68 +#: src/applications/upgradeable_contract.md:68 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x005300003ade5d10447d941a42d48b7141074cd8bade2b16520684896a5090ea) " @@ -7855,7 +7855,7 @@ msgstr "" "blob/main/listings/applications/upgradeable_contract/src/" "upgradeable_contract_v0.cairo)." -#: src/ch01/upgradeable_contract.md:71 +#: src/applications/upgradeable_contract.md:71 msgid "" "```rust\n" "use starknet::class_hash::ClassHash;\n" @@ -7949,7 +7949,7 @@ msgstr "" "}\n" "```" -#: src/ch01/upgradeable_contract.md:114 +#: src/applications/upgradeable_contract.md:114 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x017c86152badd1d665b9836571bd6b0a484f028748aa13d9b2d5d9c9192fafc6) " @@ -7965,11 +7965,11 @@ msgstr "" "blob/main/listings/applications/upgradeable_contract/src/" "upgradeable_contract_v1.cairo)." -#: src/ch01/simple_vault.md:1 +#: src/applications/simple_vault.md:1 msgid "# Simple Defi Vault" msgstr "# Simple Defi Vault" -#: src/ch01/simple_vault.md:3 +#: src/applications/simple_vault.md:3 msgid "" "This is the Cairo adaptation of the [Solidity by example Vault](https://" "solidity-by-example.org/defi/vault/).\n" @@ -7979,7 +7979,7 @@ msgstr "" "solidity-by-example.org/defi/vault/).\n" "Funciona de la siguiente manera:" -#: src/ch01/simple_vault.md:6 +#: src/applications/simple_vault.md:6 msgid "" "- When a user deposits a token, the contract calculates the amount of shares " "to mint.\n" @@ -7994,7 +7994,7 @@ msgstr "" "rendimiento y retira tanto el rendimiento como la cantidad inicial de token " "depositada." -#: src/ch01/simple_vault.md:10 +#: src/applications/simple_vault.md:10 msgid "" "```rust\n" "use starknet::{ContractAddress};\n" @@ -8220,7 +8220,7 @@ msgstr "" "\n" "```" -#: src/ch01/simple_vault.md:113 +#: src/applications/simple_vault.md:113 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -8230,11 +8230,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/applications/simple_vault/src/simple_vault.cairo)." -#: src/ch01/erc20.md:1 +#: src/applications/erc20.md:1 msgid "# ERC20 Token" msgstr "# Token ERC20" -#: src/ch01/erc20.md:3 +#: src/applications/erc20.md:3 msgid "" "Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/" "eip-20) are called ERC20 tokens. They are used to represent fungible assets." @@ -8243,12 +8243,12 @@ msgstr "" "eip-20) se denominan tokens ERC20. Se utilizan para representar activos " "fungibles." -#: src/ch01/erc20.md:5 +#: src/applications/erc20.md:5 msgid "" "To create an ERC20 conctract, it must implement the following interface:" msgstr "Para crear un contrato ERC20, debe implementar la siguiente interfaz:" -#: src/ch01/erc20.md:7 +#: src/applications/erc20.md:7 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -8314,7 +8314,7 @@ msgstr "" "}\n" "```" -#: src/ch01/erc20.md:33 +#: src/applications/erc20.md:33 msgid "" "In Starknet, function names should be written in *snake_case*. This is not " "the case in Solidity, where function names are written in *camelCase*.\n" @@ -8327,11 +8327,11 @@ msgstr "" "Por lo tanto, la interfaz ERC20 de Starknet es ligeramente diferente de la " "interfaz ERC20 de Solidity." -#: src/ch01/erc20.md:36 +#: src/applications/erc20.md:36 msgid "Here's an implementation of the ERC20 interface in Cairo:" msgstr "He aquí una implementación de la interfaz ERC20 en Cairo:" -#: src/ch01/erc20.md:38 +#: src/applications/erc20.md:38 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -8735,7 +8735,7 @@ msgstr "" "}\n" "```" -#: src/ch01/erc20.md:224 +#: src/applications/erc20.md:224 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -8745,7 +8745,7 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/applications/erc20/src/token.cairo)." -#: src/ch01/erc20.md:226 +#: src/applications/erc20.md:226 msgid "" "There's several other implementations, such as the [Open Zeppelin](https://" "docs.openzeppelin.com/contracts-cairo/0.7.0/erc20) or the [Cairo By Example]" @@ -8755,11 +8755,11 @@ msgstr "" "openzeppelin.com/contracts-cairo/0.7.0/erc20) o la [Cairo By Example]" "(https://cairo-by-example.com/examples/erc20/)." -#: src/ch01/constant-product-amm.md:1 +#: src/applications/constant-product-amm.md:1 msgid "# Constant Product AMM" msgstr "# AMM de Producto Constante" -#: src/ch01/constant-product-amm.md:3 +#: src/applications/constant-product-amm.md:3 msgid "" "This is the Cairo adaptation of the [Solidity by example Constant Product " "AMM](https://solidity-by-example.org/defi/constant-product-amm/)." @@ -8767,7 +8767,7 @@ msgstr "" "Se trata de la adaptación cairota de la [Solidez por ejemplo Producto " "Constante AMM](https://solidity-by-example.org/defi/constant-product-amm/)." -#: src/ch01/constant-product-amm.md:5 +#: src/applications/constant-product-amm.md:5 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -9367,7 +9367,7 @@ msgstr "" "}\n" "```" -#: src/ch01/constant-product-amm.md:274 +#: src/applications/constant-product-amm.md:274 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -9379,11 +9379,11 @@ msgstr "" "blob/main/listings/applications/constant_product_amm/src/" "constant_product_amm.cairo)." -#: src/ch02/write_to_any_slot.md:1 +#: src/advanced-concepts/write_to_any_slot.md:1 msgid "# Writing to any storage slot" msgstr "# Escribir en cualquier ranura de almacenamiento" -#: src/ch02/write_to_any_slot.md:3 +#: src/advanced-concepts/write_to_any_slot.md:3 msgid "" "On Starknet, a contract's storage is a map with 2^251 slots, where each slot " "is a felt which is initialized to 0.\n" @@ -9400,7 +9400,7 @@ msgstr "" "variables de almacenamiento se realizan normalmente utilizando las funciones " "`self.var.read()` y `self.var.write()`." -#: src/ch02/write_to_any_slot.md:6 +#: src/advanced-concepts/write_to_any_slot.md:6 msgid "" "Nevertheless, we can use the `storage_write_syscall` and " "`storage_read_syscall` syscalls, to write to and read from any storage " @@ -9418,7 +9418,7 @@ msgstr "" "contrato se actualiza y el método de cálculo de las direcciones de las " "variables de almacenamiento cambia, siguen siendo accesibles." -#: src/ch02/write_to_any_slot.md:9 +#: src/advanced-concepts/write_to_any_slot.md:9 msgid "" "In the following example, we use the Poseidon hash function to compute the " "address of a storage variable. Poseidon is a ZK-friendly hash function that " @@ -9433,7 +9433,7 @@ msgstr "" "calculada la dirección, utilizamos las llamadas al sistema de almacenamiento " "para interactuar con ella." -#: src/ch02/write_to_any_slot.md:11 +#: src/advanced-concepts/write_to_any_slot.md:11 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -9535,7 +9535,7 @@ msgstr "" "}\n" "```" -#: src/ch02/write_to_any_slot.md:56 +#: src/advanced-concepts/write_to_any_slot.md:56 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x033943CB781A4E63C9dcE0A1A09eAa3b617AA43CC61637C08c043a67f3fe0087) " @@ -9549,11 +9549,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/advanced-concepts/write_to_any_slot/src/contract.cairo)." -#: src/ch02/storing_arrays.md:1 +#: src/advanced-concepts/storing_arrays.md:1 msgid "# Storing Arrays" msgstr "# Almacenamiento de Arrays" -#: src/ch02/storing_arrays.md:3 +#: src/advanced-concepts/storing_arrays.md:3 msgid "" "On Starknet, complex values (e.g., tuples or structs), are stored in a " "continuous segment starting from the address of the storage variable. There " @@ -9575,7 +9575,7 @@ msgstr "" "escribir tu propia implementación del rasgo `Store` para el tipo de array " "que desees almacenar." -#: src/ch02/storing_arrays.md:5 +#: src/advanced-concepts/storing_arrays.md:5 msgid "" "> Note: While storing arrays in storage is possible, it is not always " "recommended, as the read and write operations can get very costly. For " @@ -9592,7 +9592,7 @@ msgstr "" "cada vez, se recomienda utilizar un `LegacyMap` y almacenar la longitud en " "otra variable." -#: src/ch02/storing_arrays.md:7 +#: src/advanced-concepts/storing_arrays.md:7 msgid "" "The following example demonstrates how to write a simple implementation of " "the `StorageAccess` trait for the `Array` type, allowing us to " @@ -9602,7 +9602,7 @@ msgstr "" "trait `StorageAccess` para el tipo `Array`, permitiéndonos " "almacenar arrays de hasta 255 elementos `felt252`." -#: src/ch02/storing_arrays.md:9 +#: src/advanced-concepts/storing_arrays.md:9 msgid "" "```rust\n" "impl StoreFelt252Array of Store> {\n" @@ -9750,7 +9750,7 @@ msgstr "" "}\n" "```" -#: src/ch02/storing_arrays.md:73 +#: src/advanced-concepts/storing_arrays.md:73 msgid "" "You can then import this implementation in your contract and use it to store " "arrays in storage:" @@ -9758,7 +9758,7 @@ msgstr "" "A continuación, puede importar esta implementación en su contrato y " "utilizarla para almacenar arrays en el almacenamiento:" -#: src/ch02/storing_arrays.md:75 +#: src/advanced-concepts/storing_arrays.md:75 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -9818,7 +9818,7 @@ msgstr "" "}\n" "```" -#: src/ch02/storing_arrays.md:103 +#: src/advanced-concepts/storing_arrays.md:103 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x008F8069a3Fcd7691Db46Dc3b6F9D2C0436f9200E861330957Fd780A3595da86) " @@ -9832,11 +9832,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/advanced-concepts/storing_arrays/src/contract.cairo)." -#: src/ch02/struct-mapping-key.md:1 +#: src/advanced-concepts/struct-mapping-key.md:1 msgid "# Structs as mapping keys" msgstr "# Estructuras como mapping keys" -#: src/ch02/struct-mapping-key.md:3 +#: src/advanced-concepts/struct-mapping-key.md:3 msgid "" "In order to use structs as mapping keys, you can use `#[derive(Hash)]` on " "the struct definition. This will automatically generate a hash function for " @@ -9848,7 +9848,7 @@ msgstr "" "automáticamente una función hash para la estructura que se puede utilizar " "para representar la estructura como una key en un `LegacyMap`." -#: src/ch02/struct-mapping-key.md:5 +#: src/advanced-concepts/struct-mapping-key.md:5 msgid "" "Consider the following example in which we would like to use an object of\n" "type `Pet` as a key in a `LegacyMap`. The `Pet` struct has three fields: " @@ -9860,7 +9860,7 @@ msgstr "" "campos: `nombre`, `edad` y `propietario`. Consideramos que la combinación de " "estos tres campos identifica de forma única a una mascota." -#: src/ch02/struct-mapping-key.md:8 +#: src/advanced-concepts/struct-mapping-key.md:8 msgid "" "```rust\n" "#[derive(Copy, Drop, Serde, Hash)]\n" @@ -9938,7 +9938,7 @@ msgstr "" "}\n" "```" -#: src/ch02/struct-mapping-key.md:45 +#: src/advanced-concepts/struct-mapping-key.md:45 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -9950,11 +9950,11 @@ msgstr "" "blob/main/listings/advanced-concepts/struct_as_mapping_key/src/contract." "cairo)." -#: src/ch02/hashing.md:1 +#: src/advanced-concepts/hashing.md:1 msgid "# Hashing" msgstr "# Hashing" -#: src/ch02/hashing.md:3 +#: src/advanced-concepts/hashing.md:3 msgid "" "Hashing is a cryptographic technique that allows you to transform a variable " "length input into a fixed length output.\n" @@ -9970,7 +9970,7 @@ msgstr "" "Las funciones hash son deterministas, lo que significa que la misma entrada " "siempre producirá la misma salida." -#: src/ch02/hashing.md:7 +#: src/advanced-concepts/hashing.md:7 msgid "" "The two hash functions provided by the Cairo library are `Poseidon` and " "`Pedersen`.\n" @@ -9986,7 +9986,7 @@ msgstr "" "hashes Poseidon son el estándar hoy en día ya que fueron diseñados para ser " "muy eficientes para sistemas de Zero Knowledge proof." -#: src/ch02/hashing.md:10 +#: src/advanced-concepts/hashing.md:10 msgid "" "In Cairo it's possible to hash all the types that can be converted to " "`felt252` since they implement natively the `Hash` trait. It's also possible " @@ -10000,7 +10000,7 @@ msgstr "" "el atributo `#[derive(Hash)]` pero sólo si todos los campos del struct son a " "su vez hashables." -#: src/ch02/hashing.md:12 +#: src/advanced-concepts/hashing.md:12 msgid "" "You first need to initialize a hash state with the `new` method of the " "`HashStateTrait` and then you can update it with the `update` method. You " @@ -10012,7 +10012,7 @@ msgstr "" "acumular múltiples actualizaciones. A continuación, el método `finalize` " "devuelve el valor hash final como un `felt252`." -#: src/ch02/hashing.md:14 +#: src/advanced-concepts/hashing.md:14 msgid "" "```rust\n" "# #[starknet::interface]\n" @@ -10272,11 +10272,11 @@ msgstr "" "# }\n" "```" -#: src/ch02/hash-solidity-compatible.md:1 +#: src/advanced-concepts/hash-solidity-compatible.md:1 msgid "# Hash Solidity Compatible" msgstr "# Hash compatible con Solidity" -#: src/ch02/hash-solidity-compatible.md:3 +#: src/advanced-concepts/hash-solidity-compatible.md:3 msgid "" "This contract demonstrates Keccak hashing in Cairo to match Solidity's " "keccak256. While both use Keccak, their endianness differs: Cairo is little-" @@ -10291,7 +10291,7 @@ msgstr "" "`keccak_u256s_be_inputs`, e invirtiendo los bytes del resultado con " "`u128_byte_reverse`." -#: src/ch02/hash-solidity-compatible.md:7 +#: src/advanced-concepts/hash-solidity-compatible.md:7 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -10373,7 +10373,7 @@ msgstr "" "}\n" "```" -#: src/ch02/hash-solidity-compatible.md:44 +#: src/advanced-concepts/hash-solidity-compatible.md:44 msgid "" "Play with the contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -10385,19 +10385,19 @@ msgstr "" "blob/main/listings/advanced-concepts/hash_solidity_compatible/src/contract." "cairo)." -#: src/ch02/optimisations/optimisations.md:1 +#: src/advanced-concepts/optimisations/optimisations.md:1 msgid "# Optimisations " msgstr "# Optimizaciones " -#: src/ch02/optimisations/optimisations.md:3 +#: src/advanced-concepts/optimisations/optimisations.md:3 msgid "A collection of optimisation patterns to save gas and steps." msgstr "Una colección de patrones de optimización para ahorrar gas y pasos." -#: src/ch02/optimisations/store_using_packing.md:1 +#: src/advanced-concepts/optimisations/store_using_packing.md:1 msgid "# Storage optimisation " msgstr "# Optimización del Storage " -#: src/ch02/optimisations/store_using_packing.md:3 +#: src/advanced-concepts/optimisations/store_using_packing.md:3 msgid "" "A smart contract has a limited amount of **storage slots**. Each slot can " "store a single `felt252` value.\n" @@ -10409,7 +10409,7 @@ msgstr "" "Escribir en una ranura de almacenamiento tiene un coste, por lo que queremos " "utilizar el menor número posible de ranuras de almacenamiento." -#: src/ch02/optimisations/store_using_packing.md:6 +#: src/advanced-concepts/optimisations/store_using_packing.md:6 msgid "" "In Cairo, every type is derived from the `felt252` type, which uses 252 bits " "to store a value.\n" @@ -10423,11 +10423,11 @@ msgstr "" "en almacenamiento. Por ejemplo, si queremos almacenar un valor `u8`, " "necesitamos usar un slot entero, aunque sólo necesitemos 8 bits." -#: src/ch02/optimisations/store_using_packing.md:9 +#: src/advanced-concepts/optimisations/store_using_packing.md:9 msgid "## Packing" msgstr "## Packing" -#: src/ch02/optimisations/store_using_packing.md:11 +#: src/advanced-concepts/optimisations/store_using_packing.md:11 msgid "" "When storing multiple values, we can use a technique called **packing**. " "Packing is a technique that allows us to store multiple values in a single " @@ -10439,7 +10439,7 @@ msgstr "" "múltiples valores en un único valor de felt. Esto se hace utilizando los " "bits del valor de felt para almacenar múltiples valores." -#: src/ch02/optimisations/store_using_packing.md:13 +#: src/advanced-concepts/optimisations/store_using_packing.md:13 msgid "" "For example, if we want to store two `u8` values, we can use the first 8 " "bits of the felt value to store the first `u8` value, and the last 8 bits to " @@ -10451,7 +10451,7 @@ msgstr "" "últimos 8 bits para almacenar el segundo valor `u8`. De esta forma, podemos " "almacenar dos valores `u8` en un único valor de felt." -#: src/ch02/optimisations/store_using_packing.md:15 +#: src/advanced-concepts/optimisations/store_using_packing.md:15 msgid "" "Cairo provides a built-in store using packing that you can use with the " "`StorePacking` trait." @@ -10459,7 +10459,7 @@ msgstr "" "Cairo proporciona un almacén incorporado que usa empaquetado que puedes usar " "con el trait `StorePacking`." -#: src/ch02/optimisations/store_using_packing.md:17 +#: src/advanced-concepts/optimisations/store_using_packing.md:17 msgid "" "```rust\n" "trait StorePacking {\n" @@ -10475,7 +10475,7 @@ msgstr "" "}\n" "```" -#: src/ch02/optimisations/store_using_packing.md:24 +#: src/advanced-concepts/optimisations/store_using_packing.md:24 msgid "" "This allows to store the type `T` by first packing it into the type " "`PackedT` with the `pack` function, and then storing the `PackedT` value " @@ -10489,7 +10489,7 @@ msgstr "" "`PackedT`, y luego lo desempaquetamos en el tipo `T` utilizando la función " "`unpack`." -#: src/ch02/optimisations/store_using_packing.md:26 +#: src/advanced-concepts/optimisations/store_using_packing.md:26 msgid "" "Here's an example of storing a `Time` struct with two `u8` values using the " "`StorePacking` trait:" @@ -10497,7 +10497,7 @@ msgstr "" "He aquí un ejemplo de almacenamiento de una estructura `Time` con dos " "valores `u8` utilizando el trait `StorePacking`:" -#: src/ch02/optimisations/store_using_packing.md:28 +#: src/advanced-concepts/optimisations/store_using_packing.md:28 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -10627,7 +10627,7 @@ msgstr "" "}\n" "```" -#: src/ch02/optimisations/store_using_packing.md:88 +#: src/advanced-concepts/optimisations/store_using_packing.md:88 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet-cairo1-compiler&url=https://github.com/NethermindEth/" @@ -10639,11 +10639,11 @@ msgstr "" "StarknetByExample/blob/main/listings/advanced-concepts/store_using_packing/" "src/contract.cairo)." -#: src/ch02/list.md:1 +#: src/advanced-concepts/list.md:1 msgid "# List" msgstr "# Lista" -#: src/ch02/list.md:3 +#: src/advanced-concepts/list.md:3 msgid "" "By default, there is no list type supported in Cairo, but you can use " "Alexandria. You can refer to the [Alexandria documentation](https://github." @@ -10654,17 +10654,17 @@ msgstr "" "github.com/keep-starknet-strange/alexandria/tree/main/src/storage) para más " "detalles." -#: src/ch02/list.md:5 +#: src/advanced-concepts/list.md:5 msgid "## What is `List`?" msgstr "## ¿Qué es `List`?" -#: src/ch02/list.md:7 +#: src/advanced-concepts/list.md:7 msgid "An ordered sequence of values that can be used in Starknet storage:" msgstr "" "Una secuencia ordenada de valores que puede utilizarse en el storage de " "Starknet:" -#: src/ch02/list.md:9 +#: src/advanced-concepts/list.md:9 msgid "" "```rust\n" "#[storage]\n" @@ -10680,11 +10680,11 @@ msgstr "" "}\n" "```" -#: src/ch02/list.md:16 +#: src/advanced-concepts/list.md:16 msgid "### Interface" msgstr "### Interface" -#: src/ch02/list.md:18 +#: src/advanced-concepts/list.md:18 msgid "" "```rust\n" "trait ListTrait {\n" @@ -10710,7 +10710,7 @@ msgstr "" "}\n" "```" -#: src/ch02/list.md:30 +#: src/advanced-concepts/list.md:30 msgid "" "`List` also implements `IndexView` so you can use the familiar bracket " "notation to access its members:" @@ -10718,7 +10718,7 @@ msgstr "" "`List` también implementa `IndexView` para que puedas utilizar la notación " "familiar de corchetes para acceder a sus miembros:" -#: src/ch02/list.md:32 +#: src/advanced-concepts/list.md:32 msgid "" "```rust\n" "let second = self.amounts.read()[1];\n" @@ -10728,7 +10728,7 @@ msgstr "" "let second = self.amounts.read()[1];\n" "```" -#: src/ch02/list.md:36 +#: src/advanced-concepts/list.md:36 msgid "" "Note that unlike `get`, using this bracket notation panics when accessing an " "out of bounds index." @@ -10736,11 +10736,11 @@ msgstr "" "Tenga en cuenta que, a diferencia de `get`, el uso de esta notación de " "corchetes entra en pánico cuando se accede a un índice fuera de los límites." -#: src/ch02/list.md:38 +#: src/advanced-concepts/list.md:38 msgid "### Support for custom types" msgstr "### Compatibilidad con tipos personalizados" -#: src/ch02/list.md:40 +#: src/advanced-concepts/list.md:40 msgid "" "`List` supports most of the corelib types out of the box. If you want to " "store a your own custom type in a `List`, it has to implement the `Store` " @@ -10752,15 +10752,15 @@ msgstr "" "Puedes hacer que el compilador lo derive por ti usando el atributo " "`#[derive(starknet::Store)]`." -#: src/ch02/list.md:42 +#: src/advanced-concepts/list.md:42 msgid "### Caveats" msgstr "### Advertencias" -#: src/ch02/list.md:44 +#: src/advanced-concepts/list.md:44 msgid "There are two idiosyncacies you should be aware of when using `List`" msgstr "Hay dos particularidades que debe tener en cuenta al utilizar `List`" -#: src/ch02/list.md:46 +#: src/advanced-concepts/list.md:46 msgid "" "1. The `append` operation costs 2 storage writes - one for the value itself " "and another one for updating the List's length\n" @@ -10774,7 +10774,7 @@ msgstr "" "operaciones de mutación con una única sentencia inline. Por ejemplo, `self." "amounts.read().append(42);` no funcionará. Tienes que hacerlo en 2 pasos:" -#: src/ch02/list.md:49 +#: src/advanced-concepts/list.md:49 msgid "" "```rust\n" "let mut amounts = self.amounts.read();\n" @@ -10786,15 +10786,15 @@ msgstr "" "amounts.append(42);\n" "```" -#: src/ch02/list.md:54 +#: src/advanced-concepts/list.md:54 msgid "### Dependencies" msgstr "### Dependencias" -#: src/ch02/list.md:56 +#: src/advanced-concepts/list.md:56 msgid "Update your project dependencies by in the `Scarb.toml` file:" msgstr "Actualice las dependencias de su proyecto en el archivo `Scarb.toml`:" -#: src/ch02/list.md:57 +#: src/advanced-concepts/list.md:57 msgid "" "```rust\n" "[dependencies]\n" @@ -10810,7 +10810,7 @@ msgstr "" "alexandria.git\" }\n" "```" -#: src/ch02/list.md:63 +#: src/advanced-concepts/list.md:63 msgid "" "For example, let's use `List` to create a contract that tracks a list of " "amounts and tasks:" @@ -10818,7 +10818,7 @@ msgstr "" "Por ejemplo, utilicemos `List` para crear un contrato que realice el " "seguimiento de una lista de importes y tareas:" -#: src/ch02/list.md:65 +#: src/advanced-concepts/list.md:65 msgid "" "```rust\n" "#[starknet::interface]\n" diff --git a/po/messages.pot b/po/messages.pot index d59c19a2..d1c1848f 100644 --- a/po/messages.pot +++ b/po/messages.pot @@ -28,19 +28,19 @@ msgstr "" msgid "Basics of a Starknet contract" msgstr "" -#: src/SUMMARY.md:9 src/ch00/basics/storage.md:1 src/ch00/basics/variables.md:8 +#: src/SUMMARY.md:9 src/getting-started/basics/storage.md:1 src/getting-started/basics/variables.md:8 msgid "Storage" msgstr "" -#: src/SUMMARY.md:10 src/ch00/basics/constructor.md:1 +#: src/SUMMARY.md:10 src/getting-started/basics/constructor.md:1 msgid "Constructor" msgstr "" -#: src/SUMMARY.md:11 src/ch00/basics/variables.md:1 +#: src/SUMMARY.md:11 src/getting-started/basics/variables.md:1 msgid "Variables" msgstr "" -#: src/SUMMARY.md:12 src/ch00/basics/visibility-mutability.md:1 +#: src/SUMMARY.md:12 src/getting-started/basics/visibility-mutability.md:1 msgid "Visibility and Mutability" msgstr "" @@ -48,39 +48,39 @@ msgstr "" msgid "Counter Example" msgstr "" -#: src/SUMMARY.md:14 src/ch00/basics/mappings.md:1 +#: src/SUMMARY.md:14 src/getting-started/basics/mappings.md:1 msgid "Mappings" msgstr "" -#: src/SUMMARY.md:15 src/ch00/basics/errors.md:1 +#: src/SUMMARY.md:15 src/getting-started/basics/errors.md:1 msgid "Errors" msgstr "" -#: src/SUMMARY.md:16 src/ch00/basics/events.md:1 +#: src/SUMMARY.md:16 src/getting-started/basics/events.md:1 msgid "Events" msgstr "" -#: src/SUMMARY.md:17 src/ch00/basics/storing-custom-types.md:1 +#: src/SUMMARY.md:17 src/getting-started/basics/storing-custom-types.md:1 msgid "Storing Custom Types" msgstr "" -#: src/SUMMARY.md:18 src/ch00/basics/custom-types-in-entrypoints.md:1 +#: src/SUMMARY.md:18 src/getting-started/basics/custom-types-in-entrypoints.md:1 msgid "Custom types in entrypoints" msgstr "" -#: src/SUMMARY.md:19 src/ch00/basics/documentation.md:1 +#: src/SUMMARY.md:19 src/getting-started/basics/documentation.md:1 msgid "Documentation" msgstr "" -#: src/SUMMARY.md:20 src/ch00/interacting/interacting.md:1 +#: src/SUMMARY.md:20 src/getting-started/interacting/interacting.md:1 msgid "Deploy and interact with contracts" msgstr "" -#: src/SUMMARY.md:21 src/ch00/interacting/interfaces-traits.md:1 +#: src/SUMMARY.md:21 src/getting-started/interacting/interfaces-traits.md:1 msgid "Contract interfaces and Traits generation" msgstr "" -#: src/SUMMARY.md:22 src/ch00/interacting/calling_other_contracts.md:1 +#: src/SUMMARY.md:22 src/getting-started/interacting/calling_other_contracts.md:1 msgid "Calling other contracts" msgstr "" @@ -104,27 +104,27 @@ msgstr "" msgid "LegacyMap" msgstr "" -#: src/SUMMARY.md:28 src/ch00/cairo_cheatsheet/arrays.md:1 +#: src/SUMMARY.md:28 src/getting-started/cairo_cheatsheet/arrays.md:1 msgid "Arrays" msgstr "" -#: src/SUMMARY.md:29 src/ch00/cairo_cheatsheet/loop.md:1 +#: src/SUMMARY.md:29 src/getting-started/cairo_cheatsheet/loop.md:1 msgid "Loop" msgstr "" -#: src/SUMMARY.md:30 src/ch00/cairo_cheatsheet/match.md:1 +#: src/SUMMARY.md:30 src/getting-started/cairo_cheatsheet/match.md:1 msgid "Match" msgstr "" -#: src/SUMMARY.md:31 src/ch00/cairo_cheatsheet/tuples.md:1 +#: src/SUMMARY.md:31 src/getting-started/cairo_cheatsheet/tuples.md:1 msgid "Tuples" msgstr "" -#: src/SUMMARY.md:32 src/ch00/cairo_cheatsheet/struct.md:1 +#: src/SUMMARY.md:32 src/getting-started/cairo_cheatsheet/struct.md:1 msgid "Struct" msgstr "" -#: src/SUMMARY.md:33 src/ch00/cairo_cheatsheet/type_casting.md:1 +#: src/SUMMARY.md:33 src/getting-started/cairo_cheatsheet/type_casting.md:1 msgid "Type casting" msgstr "" @@ -132,7 +132,7 @@ msgstr "" msgid "Applications examples" msgstr "" -#: src/SUMMARY.md:37 src/ch01/upgradeable_contract.md:1 +#: src/SUMMARY.md:37 src/applications/upgradeable_contract.md:1 msgid "Upgradeable Contract" msgstr "" @@ -140,11 +140,11 @@ msgstr "" msgid "Defi Vault" msgstr "" -#: src/SUMMARY.md:39 src/ch01/erc20.md:1 +#: src/SUMMARY.md:39 src/applications/erc20.md:1 msgid "ERC20 Token" msgstr "" -#: src/SUMMARY.md:40 src/ch01/constant-product-amm.md:1 +#: src/SUMMARY.md:40 src/applications/constant-product-amm.md:1 msgid "Constant Product AMM" msgstr "" @@ -152,11 +152,11 @@ msgstr "" msgid "Advanced concepts" msgstr "" -#: src/SUMMARY.md:44 src/ch02/write_to_any_slot.md:1 +#: src/SUMMARY.md:44 src/advanced-concepts/write_to_any_slot.md:1 msgid "Writing to any storage slot" msgstr "" -#: src/SUMMARY.md:45 src/ch02/storing_arrays.md:1 +#: src/SUMMARY.md:45 src/advanced-concepts/storing_arrays.md:1 msgid "Storing Arrays" msgstr "" @@ -164,11 +164,11 @@ msgstr "" msgid "Struct as mapping key" msgstr "" -#: src/SUMMARY.md:47 src/ch02/hash-solidity-compatible.md:1 +#: src/SUMMARY.md:47 src/advanced-concepts/hash-solidity-compatible.md:1 msgid "Hash Solidity Compatible" msgstr "" -#: src/SUMMARY.md:48 src/ch02/optimisations/optimisations.md:1 +#: src/SUMMARY.md:48 src/advanced-concepts/optimisations/optimisations.md:1 msgid "Optimisations" msgstr "" @@ -176,7 +176,7 @@ msgstr "" msgid "Storage Optimisations" msgstr "" -#: src/SUMMARY.md:50 src/ch02/list.md:1 +#: src/SUMMARY.md:50 src/advanced-concepts/list.md:1 msgid "List" msgstr "" @@ -262,45 +262,45 @@ msgid "" "with simple examples" msgstr "" -#: src/ch00/basics/introduction.md:1 +#: src/getting-started/basics/introduction.md:1 msgid "Basics of Smart Contracts in Cairo" msgstr "" -#: src/ch00/basics/introduction.md:3 +#: src/getting-started/basics/introduction.md:3 msgid "" "The following chapters will introduce you to Starknet smart contracts and " "how to write them in Cairo." msgstr "" -#: src/ch00/basics/storage.md:3 +#: src/getting-started/basics/storage.md:3 msgid "Here's the most minimal contract you can write in Cairo:" msgstr "" -#: src/ch00/basics/storage.md:13 +#: src/getting-started/basics/storage.md:13 msgid "" "Storage is a struct annoted with `#[storage]`. Every contract must have one " "and only one storage. It's a key-value store, where each key will be mapped " "to a storage address of the contract's storage space." msgstr "" -#: src/ch00/basics/storage.md:16 +#: src/getting-started/basics/storage.md:16 msgid "" "You can define [storage variables](./variables.md#storage-variables) in your " "contract, and then use them to store and retrieve data." msgstr "" -#: src/ch00/basics/storage.md:29 +#: src/getting-started/basics/storage.md:29 msgid "" "Actually these two contracts have the same underlying Sierra program. From " "the compiler's perspective, the storage variables don't exist until they are " "used." msgstr "" -#: src/ch00/basics/storage.md:32 +#: src/getting-started/basics/storage.md:32 msgid "You can also read about [storing custom types](./storing-custom-types.md)" msgstr "" -#: src/ch00/basics/constructor.md:3 +#: src/getting-started/basics/constructor.md:3 msgid "" "Constructors are a special type of function that runs only once when " "deploying a contract, and can be used to initialize the state of the " @@ -309,74 +309,74 @@ msgid "" "Also, a good practice consists in naming that function `constructor`." msgstr "" -#: src/ch00/basics/constructor.md:5 +#: src/getting-started/basics/constructor.md:5 msgid "" "Here's a simple example that demonstrates how to initialize the state of a " "contract on deployment by defining logic inside a constructor." msgstr "" -#: src/ch00/basics/constructor.md:17 +#: src/getting-started/basics/constructor.md:17 msgid "" "// The constructor is decorated with a `#[constructor]` attribute.\n" " // It is not inside an `impl` block.\n" msgstr "" -#: src/ch00/basics/constructor.md:25 +#: src/getting-started/basics/constructor.md:25 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x017fd6558e67451dA583d123D77F4e2651E91502D08F8F8432355293b11e1f8F) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/constructor/src/constructor.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/constructor/src/constructor.cairo)." msgstr "" -#: src/ch00/basics/variables.md:3 +#: src/getting-started/basics/variables.md:3 msgid "There are 3 types of variables in Cairo contracts:" msgstr "" -#: src/ch00/basics/variables.md:5 +#: src/getting-started/basics/variables.md:5 msgid "Local" msgstr "" -#: src/ch00/basics/variables.md:6 +#: src/getting-started/basics/variables.md:6 msgid "declared inside a function" msgstr "" -#: src/ch00/basics/variables.md:7 +#: src/getting-started/basics/variables.md:7 msgid "not stored on the blockchain" msgstr "" -#: src/ch00/basics/variables.md:9 +#: src/getting-started/basics/variables.md:9 msgid "declared in the [Storage](./storage.md) of a contract" msgstr "" -#: src/ch00/basics/variables.md:10 +#: src/getting-started/basics/variables.md:10 msgid "can be accessed from one execution to another" msgstr "" -#: src/ch00/basics/variables.md:11 +#: src/getting-started/basics/variables.md:11 msgid "Global" msgstr "" -#: src/ch00/basics/variables.md:12 +#: src/getting-started/basics/variables.md:12 msgid "provides information about the blockchain" msgstr "" -#: src/ch00/basics/variables.md:13 +#: src/getting-started/basics/variables.md:13 msgid "accessed anywhere, even within library functions" msgstr "" -#: src/ch00/basics/variables.md:15 +#: src/getting-started/basics/variables.md:15 msgid "Local Variables" msgstr "" -#: src/ch00/basics/variables.md:17 +#: src/getting-started/basics/variables.md:17 msgid "" "Local variables are used and accessed within the scope of a specific " "function or block of code. They are temporary and exist only for the " "duration of that particular function or block execution." msgstr "" -#: src/ch00/basics/variables.md:19 +#: src/getting-started/basics/variables.md:19 msgid "" "Local variables are stored in memory and are not stored on the blockchain. " "This means they cannot be accessed from one execution to another. Local " @@ -385,148 +385,148 @@ msgid "" "intermediate values." msgstr "" -#: src/ch00/basics/variables.md:21 +#: src/getting-started/basics/variables.md:21 msgid "Here's a simple example of a contract with only local variables:" msgstr "" -#: src/ch00/basics/variables.md:37 +#: src/getting-started/basics/variables.md:37 msgid "" "// This variable is local to the current block. It can't be accessed once it " "goes out of scope.\n" msgstr "" -#: src/ch00/basics/variables.md:41 +#: src/getting-started/basics/variables.md:41 msgid "" "// The scope of a code block allows for local variable declaration\n" " // We can access variables defined in higher scopes.\n" msgstr "" -#: src/ch00/basics/variables.md:50 +#: src/getting-started/basics/variables.md:50 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x015B3a10F9689BeD741Ca3C210017BC097122CeF76f3cAA191A20ff8b9b56b96) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/variables/src/local_variables.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/variables/src/local_variables.cairo)." msgstr "" -#: src/ch00/basics/variables.md:52 +#: src/getting-started/basics/variables.md:52 msgid "Storage Variables" msgstr "" -#: src/ch00/basics/variables.md:54 +#: src/getting-started/basics/variables.md:54 msgid "" "Storage variables are persistent data stored on the blockchain. They can be " "accessed from one execution to another, allowing the contract to remember " "and update information over time." msgstr "" -#: src/ch00/basics/variables.md:56 +#: src/getting-started/basics/variables.md:56 msgid "" "To write or update a storage variable, you need to interact with the " "contract through an external entrypoint by sending a transaction." msgstr "" -#: src/ch00/basics/variables.md:58 +#: src/getting-started/basics/variables.md:58 msgid "" "On the other hand, you can read state variables, for free, without any " "transaction, simply by interacting with a node." msgstr "" -#: src/ch00/basics/variables.md:60 +#: src/getting-started/basics/variables.md:60 msgid "Here's a simple example of a contract with one storage variable:" msgstr "" -#: src/ch00/basics/variables.md:70 +#: src/getting-started/basics/variables.md:70 msgid "" "// All storage variables are contained in a struct called Storage\n" " // annotated with the `#[storage]` attribute\n" msgstr "" -#: src/ch00/basics/variables.md:74 +#: src/getting-started/basics/variables.md:74 msgid "// Storage variable holding a number\n" msgstr "" -#: src/ch00/basics/variables.md:80 +#: src/getting-started/basics/variables.md:80 msgid "" "// Write to storage variables by sending a transaction that calls an " "external function\n" msgstr "" -#: src/ch00/basics/variables.md:85 +#: src/getting-started/basics/variables.md:85 msgid "// Read from storage variables without sending transactions\n" msgstr "" -#: src/ch00/basics/variables.md:92 +#: src/getting-started/basics/variables.md:92 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x06eA827B32875483709b785A7F9e846a52776Cd8D42C3fE696218c2624b0DCCa) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/variables/src/storage_variables.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/variables/src/storage_variables.cairo)." msgstr "" -#: src/ch00/basics/variables.md:94 +#: src/getting-started/basics/variables.md:94 msgid "Global Variables" msgstr "" -#: src/ch00/basics/variables.md:96 +#: src/getting-started/basics/variables.md:96 msgid "" "Global variables are predefined variables that provide information about the " "blockchain and the current execution environment. They can be accessed at " "any time and from anywhere!" msgstr "" -#: src/ch00/basics/variables.md:98 +#: src/getting-started/basics/variables.md:98 msgid "" "In Starknet, you can access global variables by using specific functions " "contained in the starknet core libraries." msgstr "" -#: src/ch00/basics/variables.md:100 +#: src/getting-started/basics/variables.md:100 msgid "" "For example, the `get_caller_address` function returns the address of the " "caller of the current transaction, and the `get_contract_address` function " "returns the address of the current contract." msgstr "" -#: src/ch00/basics/variables.md:109 +#: src/getting-started/basics/variables.md:109 msgid "// import the required functions from the starknet core library\n" msgstr "" -#: src/ch00/basics/variables.md:118 +#: src/getting-started/basics/variables.md:118 msgid "// Call the get_caller_address function to get the sender address\n" msgstr "" -#: src/ch00/basics/variables.md:120 +#: src/getting-started/basics/variables.md:120 msgid "// ...\n" msgstr "" -#: src/ch00/basics/variables.md:125 +#: src/getting-started/basics/variables.md:125 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x05bD2F3943bd4e030f85678b55b2EC2C1be939e32388530FB20ED967B3Be433F) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/variables/src/global_variables.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/variables/src/global_variables.cairo)." msgstr "" -#: src/ch00/basics/visibility-mutability.md:3 +#: src/getting-started/basics/visibility-mutability.md:3 msgid "Visibility" msgstr "" -#: src/ch00/basics/visibility-mutability.md:5 +#: src/getting-started/basics/visibility-mutability.md:5 msgid "There are two types of functions in Starknet contracts:" msgstr "" -#: src/ch00/basics/visibility-mutability.md:7 +#: src/getting-started/basics/visibility-mutability.md:7 msgid "Functions that are accessible externally and can be called by anyone." msgstr "" -#: src/ch00/basics/visibility-mutability.md:8 +#: src/getting-started/basics/visibility-mutability.md:8 msgid "" "Functions that are only accessible internally and can only be called by " "other functions in the contract." msgstr "" -#: src/ch00/basics/visibility-mutability.md:10 +#: src/getting-started/basics/visibility-mutability.md:10 msgid "" "These functions are also typically divided into two different " "implementations blocks. The first `impl` block for externally accessible " @@ -537,11 +537,11 @@ msgid "" "all the functions inside this block are private by default." msgstr "" -#: src/ch00/basics/visibility-mutability.md:12 +#: src/getting-started/basics/visibility-mutability.md:12 msgid "State Mutability" msgstr "" -#: src/ch00/basics/visibility-mutability.md:14 +#: src/getting-started/basics/visibility-mutability.md:14 msgid "" "Regardless of whether a function is internal or external, it can either " "modify the contract's state or not. When we declare functions that interact " @@ -550,19 +550,19 @@ msgid "" "parameter of the function. This can be done in two different ways:" msgstr "" -#: src/ch00/basics/visibility-mutability.md:17 +#: src/getting-started/basics/visibility-mutability.md:17 msgid "" "If we want our function to be able to mutate the state of the contract, we " "pass it by reference like this: `ref self: ContractState`." msgstr "" -#: src/ch00/basics/visibility-mutability.md:18 +#: src/getting-started/basics/visibility-mutability.md:18 msgid "" "If we want our function to be read-only and not mutate the state of the " "contract, we pass it by snapshot like this: `self: @ContractState`." msgstr "" -#: src/ch00/basics/visibility-mutability.md:20 +#: src/getting-started/basics/visibility-mutability.md:20 msgid "" "Read-only functions, also called view functions, can be directly called " "without making a transaction. You can interact with them directly through a " @@ -571,17 +571,17 @@ msgid "" "called by making a transaction." msgstr "" -#: src/ch00/basics/visibility-mutability.md:23 +#: src/getting-started/basics/visibility-mutability.md:23 msgid "" "Internal functions can't be called externally, but the same principle " "applies regarding state mutability." msgstr "" -#: src/ch00/basics/visibility-mutability.md:25 +#: src/getting-started/basics/visibility-mutability.md:25 msgid "Let's take a look at a simple example contract to see these in action:" msgstr "" -#: src/ch00/basics/visibility-mutability.md:42 +#: src/getting-started/basics/visibility-mutability.md:42 msgid "" "// The `#[abi(embed_v0)]` attribute indicates that all the functions in this " "implementation can be called externally.\n" @@ -589,7 +589,7 @@ msgid "" "implementation internal.\n" msgstr "" -#: src/ch00/basics/visibility-mutability.md:46 +#: src/getting-started/basics/visibility-mutability.md:46 msgid "" "// The `set` function can be called externally because it is written inside " "an implementation marked as `#[abi(embed_v0)]`.\n" @@ -597,7 +597,7 @@ msgid "" "reference.\n" msgstr "" -#: src/ch00/basics/visibility-mutability.md:52 +#: src/getting-started/basics/visibility-mutability.md:52 msgid "" "// The `get` function can be called externally because it is written inside " "an implementation marked as `#[abi(embed_v0)]`.\n" @@ -605,12 +605,12 @@ msgid "" "snapshot: it is only a \"view\" function.\n" msgstr "" -#: src/ch00/basics/visibility-mutability.md:55 +#: src/getting-started/basics/visibility-mutability.md:55 msgid "" "// We can call an internal function from any functions within the contract\n" msgstr "" -#: src/ch00/basics/visibility-mutability.md:60 +#: src/getting-started/basics/visibility-mutability.md:60 msgid "" "// The lack of the `external` attribute indicates that all the functions in " "this implementation can only be called internally.\n" @@ -618,7 +618,7 @@ msgid "" "internal trait allowing us to call internal functions.\n" msgstr "" -#: src/ch00/basics/visibility-mutability.md:64 +#: src/getting-started/basics/visibility-mutability.md:64 msgid "" "// The `_read_value` function is outside the implementation that is marked " "as `#[abi(embed_v0)]`, so it's an _internal_ function\n" @@ -627,64 +627,64 @@ msgid "" "snapshot: it is only a \"view\" function.\n" msgstr "" -#: src/ch00/basics/visibility-mutability.md:73 +#: src/getting-started/basics/visibility-mutability.md:73 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x0071dE3093AB58053b0292C225aa0eED40293e7694A0042685FF6D813d39889F) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/visibility/src/visibility.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/visibility/src/visibility.cairo)." msgstr "" -#: src/ch00/basics/counter.md:1 +#: src/getting-started/basics/counter.md:1 msgid "Simple Counter" msgstr "" -#: src/ch00/basics/counter.md:3 +#: src/getting-started/basics/counter.md:3 msgid "This is a simple counter contract." msgstr "" -#: src/ch00/basics/counter.md:5 +#: src/getting-started/basics/counter.md:5 msgid "Here's how it works:" msgstr "" -#: src/ch00/basics/counter.md:7 +#: src/getting-started/basics/counter.md:7 msgid "" "The contract has a state variable called 'counter' that is initialized to 0." msgstr "" -#: src/ch00/basics/counter.md:9 +#: src/getting-started/basics/counter.md:9 msgid "When a user calls 'increment', the contract increments the counter by 1." msgstr "" -#: src/ch00/basics/counter.md:11 +#: src/getting-started/basics/counter.md:11 msgid "When a user calls 'decrement', the contract decrements the counter by 1." msgstr "" -#: src/ch00/basics/counter.md:25 +#: src/getting-started/basics/counter.md:25 msgid "// Counter variable\n" msgstr "" -#: src/ch00/basics/counter.md:31 +#: src/getting-started/basics/counter.md:31 msgid "// Store initial value\n" msgstr "" -#: src/ch00/basics/counter.md:42 +#: src/getting-started/basics/counter.md:42 msgid "// Store counter value + 1\n" msgstr "" -#: src/ch00/basics/counter.md:47 +#: src/getting-started/basics/counter.md:47 msgid "// Store counter value - 1\n" msgstr "" -#: src/ch00/basics/counter.md:54 +#: src/getting-started/basics/counter.md:54 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x01664a69Fe701a1df7Bb0ae4A353792d0cf4E27146ee860075cbf6108b1D5718) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/counter/src/contracts.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/counter/src/contracts.cairo)." msgstr "" -#: src/ch00/basics/mappings.md:3 +#: src/getting-started/basics/mappings.md:3 msgid "" "Maps are a key-value data structure used to store data within a smart " "contract. In Cairo they are implemented using the `LegacyMap` type. It's " @@ -692,7 +692,7 @@ msgid "" "`Storage` struct of a contract and that it can't be used elsewhere." msgstr "" -#: src/ch00/basics/mappings.md:5 +#: src/getting-started/basics/mappings.md:5 msgid "" "Here we demonstrate how to use the `LegacyMap` type within a Cairo contract, " "to map between a key of type `ContractAddress` and value of type `felt252`. " @@ -702,18 +702,18 @@ msgid "" "calling the `read()` method and passing in the relevant key." msgstr "" -#: src/ch00/basics/mappings.md:7 +#: src/getting-started/basics/mappings.md:7 msgid "Some additional notes:" msgstr "" -#: src/ch00/basics/mappings.md:9 +#: src/getting-started/basics/mappings.md:9 msgid "" "More complex key-value mappings are possible, for example we could use " "`LegacyMap::<(ContractAddress, ContractAddress), felt252>` to create an " "allowance on an ERC20 token contract." msgstr "" -#: src/ch00/basics/mappings.md:11 +#: src/getting-started/basics/mappings.md:11 msgid "" "In mappings, the address of the value at key `k_1,...,k_n` is " "`h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)` where `ℎ` is the " @@ -722,19 +722,19 @@ msgid "" "Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Contracts/contract-storage/#storage_variables)." msgstr "" -#: src/ch00/basics/mappings.md:28 +#: src/getting-started/basics/mappings.md:28 msgid "// The `LegacyMap` type is only available inside the `Storage` struct.\n" msgstr "" -#: src/ch00/basics/mappings.md:44 +#: src/getting-started/basics/mappings.md:44 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x06214AB4c23Cc545bf2221D465eB83aFb7412779AD498BD48a724B3F645E3505) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/mappings/src/mappings.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/mappings/src/mappings.cairo)." msgstr "" -#: src/ch00/basics/errors.md:3 +#: src/getting-started/basics/errors.md:3 msgid "" "Errors can be used to handle validation and other conditions that may occur " "during the execution of a smart contract. If an error is thrown during the " @@ -742,18 +742,18 @@ msgid "" "made during the transaction are reverted." msgstr "" -#: src/ch00/basics/errors.md:6 +#: src/getting-started/basics/errors.md:6 msgid "To throw an error, use the `assert` or `panic` functions:" msgstr "" -#: src/ch00/basics/errors.md:8 +#: src/getting-started/basics/errors.md:8 msgid "" "`assert` is used to validate conditions. If the check fails, an error is " "thrown along with a specified value, often a message. It's similar to the " "`require` statement in Solidity." msgstr "" -#: src/ch00/basics/errors.md:12 +#: src/getting-started/basics/errors.md:12 msgid "" "`panic` immediately halt the execution with the given error value. It should " "be used when the condition to check is complex and for internal errors. It's " @@ -761,73 +761,73 @@ msgid "" "be able to directly pass a felt252 as the error value)" msgstr "" -#: src/ch00/basics/errors.md:16 +#: src/getting-started/basics/errors.md:16 msgid "Here's a simple example that demonstrates the use of these functions:" msgstr "" -#: src/ch00/basics/errors.md:32 +#: src/getting-started/basics/errors.md:32 msgid "" "// Assert used to validate a condition\n" " // and abort execution if the condition is not met\n" msgstr "" -#: src/ch00/basics/errors.md:39 +#: src/getting-started/basics/errors.md:39 msgid "// Panic used to abort execution directly\n" msgstr "" -#: src/ch00/basics/errors.md:46 +#: src/getting-started/basics/errors.md:46 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x0022664463FF0b711CC9B549a9E87d65A0882bB1D29338C4108696B8F2216a40) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/errors/src/simple_errors.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/errors/src/simple_errors.cairo)." msgstr "" -#: src/ch00/basics/errors.md:48 +#: src/getting-started/basics/errors.md:48 msgid "Custom errors" msgstr "" -#: src/ch00/basics/errors.md:50 +#: src/getting-started/basics/errors.md:50 msgid "" "You can make error handling easier by defining your error codes in a " "specific module." msgstr "" -#: src/ch00/basics/errors.md:85 +#: src/getting-started/basics/errors.md:85 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x0501CD5da5B453a18515B5A20b8029bd7583DFE7a399ad9f79c284F7829e4A57) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/errors/src/custom_errors.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/errors/src/custom_errors.cairo)." msgstr "" -#: src/ch00/basics/errors.md:87 +#: src/getting-started/basics/errors.md:87 msgid "Vault example" msgstr "" -#: src/ch00/basics/errors.md:89 +#: src/getting-started/basics/errors.md:89 msgid "" "Here's another example that demonstrates the use of errors in a more complex " "contract:" msgstr "" -#: src/ch00/basics/errors.md:93 +#: src/getting-started/basics/errors.md:93 msgid "// you can define more errors here\n" msgstr "" -#: src/ch00/basics/errors.md:125 +#: src/getting-started/basics/errors.md:125 msgid "// Or using panic:\n" msgstr "" -#: src/ch00/basics/errors.md:137 +#: src/getting-started/basics/errors.md:137 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x020C2da26F42A28Ef54ED428eF1810FE433784b055f9bF315C5d992b1579C268) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/errors/src/vault_errors.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/errors/src/vault_errors.cairo)." msgstr "" -#: src/ch00/basics/events.md:3 +#: src/getting-started/basics/events.md:3 msgid "" "Events are a way to emit data from a contract. All events must be defined in " "the `Event` enum, which must be annotated with the `#[event]` attribute. An " @@ -838,46 +838,46 @@ msgid "" "member." msgstr "" -#: src/ch00/basics/events.md:6 +#: src/getting-started/basics/events.md:6 msgid "" "Here's a simple example of a contract using events that emit an event each " "time a counter is incremented by the \"increment\" function:" msgstr "" -#: src/ch00/basics/events.md:18 +#: src/getting-started/basics/events.md:18 msgid "// Counter value\n" msgstr "" -#: src/ch00/basics/events.md:24 +#: src/getting-started/basics/events.md:24 msgid "" "// The event enum must be annotated with the `#[event]` attribute.\n" " // It must also derive the `Drop` and `starknet::Event` traits.\n" msgstr "" -#: src/ch00/basics/events.md:31 +#: src/getting-started/basics/events.md:31 msgid "" "// By deriving the `starknet::Event` trait, we indicate to the compiler " "that\n" " // this struct will be used when emitting events.\n" msgstr "" -#: src/ch00/basics/events.md:40 +#: src/getting-started/basics/events.md:40 msgid "// The `#[key]` attribute indicates that this event will be indexed.\n" msgstr "" -#: src/ch00/basics/events.md:52 +#: src/getting-started/basics/events.md:52 msgid "// Emit event\n" msgstr "" -#: src/ch00/basics/events.md:66 +#: src/getting-started/basics/events.md:66 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x022e3B59518EA04aBb5da671ea04ecC3a154400f226d2Df38eFE146741b9E2F6) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/events/src/counter.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/events/src/counter.cairo)." msgstr "" -#: src/ch00/basics/storing-custom-types.md:3 +#: src/getting-started/basics/storing-custom-types.md:3 msgid "" "While native types can be stored in a contract's storage without any " "additional work, custom types require a bit more work. This is because at " @@ -887,19 +887,19 @@ msgid "" "unless it contains arrays or dictionaries." msgstr "" -#: src/ch00/basics/storing-custom-types.md:10 +#: src/getting-started/basics/storing-custom-types.md:10 msgid "" "// Deriving the starknet::Store trait\n" "// allows us to store the `Person` struct in the contract's storage.\n" msgstr "" -#: src/ch00/basics/storing-custom-types.md:37 +#: src/getting-started/basics/storing-custom-types.md:37 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/storing_custom_types/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/storing_custom_types/src/contract.cairo)." msgstr "" -#: src/ch00/basics/custom-types-in-entrypoints.md:3 +#: src/getting-started/basics/custom-types-in-entrypoints.md:3 msgid "" "Using custom types in entrypoints requires our type to implement the `Serde` " "trait. This is because when calling an entrypoint, the input is sent as an " @@ -909,43 +909,43 @@ msgid "" "Thankfully, we can just derive the `Serde` trait for our custom type." msgstr "" -#: src/ch00/basics/custom-types-in-entrypoints.md:18 +#: src/getting-started/basics/custom-types-in-entrypoints.md:18 msgid "" "// Deriving the `Serde` trait allows us to use\n" " // the Person type as an entrypoint parameter and return value\n" msgstr "" -#: src/ch00/basics/custom-types-in-entrypoints.md:37 +#: src/getting-started/basics/custom-types-in-entrypoints.md:37 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/custom_type_serde/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/custom_type_serde/src/contract.cairo)." msgstr "" -#: src/ch00/basics/documentation.md:3 +#: src/getting-started/basics/documentation.md:3 msgid "" "It's important to take the time to document your code. It will helps " "developers and users to understand the contract and its functionalities." msgstr "" -#: src/ch00/basics/documentation.md:5 +#: src/getting-started/basics/documentation.md:5 msgid "In Cairo, you can add comments with `//`." msgstr "" -#: src/ch00/basics/documentation.md:7 +#: src/getting-started/basics/documentation.md:7 msgid "Best Practices:" msgstr "" -#: src/ch00/basics/documentation.md:9 +#: src/getting-started/basics/documentation.md:9 msgid "" "Since Cairo 1, the community has adopted a [Rust-like documentation " "style](https://doc.rust-lang.org/rust-by-example/meta/doc.html)." msgstr "" -#: src/ch00/basics/documentation.md:11 +#: src/getting-started/basics/documentation.md:11 msgid "Contract Interface:" msgstr "" -#: src/ch00/basics/documentation.md:13 +#: src/getting-started/basics/documentation.md:13 msgid "" "In smart contracts, you will often have a trait that defines the contract's " "interface (with `#[starknet::interface]`). This is the perfect place to " @@ -953,7 +953,7 @@ msgid "" "the contract entry points. You can follow this template:" msgstr "" -#: src/ch00/basics/documentation.md:19 +#: src/getting-started/basics/documentation.md:19 msgid "" "/// High-level description of the function\n" " ///\n" @@ -967,33 +967,33 @@ msgid "" " /// High-level description of the return value\n" msgstr "" -#: src/ch00/basics/documentation.md:33 +#: src/getting-started/basics/documentation.md:33 msgid "" "Keep in mind that this should not describe the implementation details of the " "function, but rather the high-level purpose and functionality of the " "contract from the perspective of a user." msgstr "" -#: src/ch00/basics/documentation.md:35 +#: src/getting-started/basics/documentation.md:35 msgid "Implementation Details:" msgstr "" -#: src/ch00/basics/documentation.md:37 +#: src/getting-started/basics/documentation.md:37 msgid "" "When writing the logic of the contract, you can add comments to describe the " "technical implementation details of the functions." msgstr "" -#: src/ch00/basics/documentation.md:39 +#: src/getting-started/basics/documentation.md:39 msgid "" "Avoid over-commenting: Comments should provide additional value and clarity." msgstr "" -#: src/ch00/interacting/interacting.md:3 +#: src/getting-started/interacting/interacting.md:3 msgid "In this chapter, we will see how to deploy and interact with contracts." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:3 +#: src/getting-started/interacting/interfaces-traits.md:3 msgid "" "Contract interfaces define the structure and behavior of a contract, serving " "as the contract's public ABI. They list all the function signatures that a " @@ -1002,13 +1002,13 @@ msgid "" "Book](https://book.cairo-lang.org/ch99-01-02-a-simple-contract.html)." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:5 +#: src/getting-started/interacting/interfaces-traits.md:5 msgid "" "In cairo, to specify the interface you need to define a trait annotated with " "`#[starknet::interface]` and then implement that trait in the contract." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:7 +#: src/getting-started/interacting/interfaces-traits.md:7 msgid "" "When a function needs to access the contract state, it must have a `self` " "parameter of type `ContractState`. This implies that the corresponding " @@ -1017,7 +1017,7 @@ msgid "" "contract interface must have this `self` parameter of type `TContractState`." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:9 +#: src/getting-started/interacting/interfaces-traits.md:9 msgid "" "You can use the `#[generate_trait]` attribute to implicitly generate the " "trait for a specific implementation block. This attribute automatically " @@ -1029,83 +1029,83 @@ msgid "" "l1 handler." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:11 +#: src/getting-started/interacting/interfaces-traits.md:11 msgid "In summary, there's two ways to handle interfaces:" msgstr "" -#: src/ch00/interacting/interfaces-traits.md:13 +#: src/getting-started/interacting/interfaces-traits.md:13 msgid "Explicitly, by defining a trait annoted with `#[starknet::interface]`" msgstr "" -#: src/ch00/interacting/interfaces-traits.md:14 +#: src/getting-started/interacting/interfaces-traits.md:14 msgid "" "Implicitly, by using `#[generate_trait]` combined with the " "#\\[abi(per_item)\\]\\` attributes, and annotating each function inside the " "implementation block with the appropriate attribute." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:16 +#: src/getting-started/interacting/interfaces-traits.md:16 msgid "Explicit interface" msgstr "" -#: src/ch00/interacting/interfaces-traits.md:45 +#: src/getting-started/interacting/interfaces-traits.md:45 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/interfaces_traits/src/explicit.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/interfaces_traits/src/explicit.cairo)." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:47 +#: src/getting-started/interacting/interfaces-traits.md:47 msgid "Implicit interface" msgstr "" -#: src/ch00/interacting/interfaces-traits.md:73 +#: src/getting-started/interacting/interfaces-traits.md:73 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/interfaces_traits/src/implicit.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/interfaces_traits/src/implicit.cairo)." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:75 +#: src/getting-started/interacting/interfaces-traits.md:75 msgid "" "Note: You can import an implicitly generated contract interface with `use " "contract::{GeneratedContractInterface}`. However, the `Dispatcher` will not " "be generated automatically." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:77 +#: src/getting-started/interacting/interfaces-traits.md:77 msgid "Internal functions" msgstr "" -#: src/ch00/interacting/interfaces-traits.md:79 +#: src/getting-started/interacting/interfaces-traits.md:79 msgid "" "You can also use `#[generate_trait]` for your internal functions. Since this " "trait is generated in the context of the contract, you can define pure " "functions as well (functions without the `self` parameter)." msgstr "" -#: src/ch00/interacting/interfaces-traits.md:127 +#: src/getting-started/interacting/interfaces-traits.md:127 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/interfaces_traits/src/implicit_internal.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/interfaces_traits/src/implicit_internal.cairo)." msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:3 +#: src/getting-started/interacting/calling_other_contracts.md:3 msgid "There are two different ways to call other contracts in Cairo." msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:5 +#: src/getting-started/interacting/calling_other_contracts.md:5 msgid "" "The easiest way to call other contracts is by using the dispatcher of the " "contract you want to call. You can read more about Dispatchers in the [Cairo " "Book](https://book.cairo-lang.org/ch99-02-02-contract-dispatcher-library-dispatcher-and-system-calls.html#contract-dispatcher)" msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:8 +#: src/getting-started/interacting/calling_other_contracts.md:8 msgid "" "The other way is to use the `starknet::call_contract_syscall` syscall " "yourself. However, this method is not recommended." msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:10 +#: src/getting-started/interacting/calling_other_contracts.md:10 msgid "" "In order to call other contracts using dispatchers, you will need to define " "the called contract's interface as a trait annotated with the " @@ -1113,54 +1113,54 @@ msgid "" "`IContractDispatcher` and `IContractDispatcherTrait` items in your contract." msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:34 +#: src/getting-started/interacting/calling_other_contracts.md:34 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x015c3Bb6D0DE26b64FEAF9A8f4655CfADb5c128bF4510398972704ee12775DB1) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/calling_other_contracts/src/callee.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/calling_other_contracts/src/callee.cairo)." msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:38 +#: src/getting-started/interacting/calling_other_contracts.md:38 msgid "" "// We need to have the interface of the callee contract defined\n" "// so that we can import the Dispatcher.\n" msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:53 +#: src/getting-started/interacting/calling_other_contracts.md:53 msgid "// We import the Dispatcher of the called contract\n" msgstr "" -#: src/ch00/interacting/calling_other_contracts.md:68 +#: src/getting-started/interacting/calling_other_contracts.md:68 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x05fa8aF796343d2f22c53C17149386b67B7AC4aB52D9e308Aa507C185aA44778) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/calling_other_contracts/src/caller.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/calling_other_contracts/src/caller.cairo)." msgstr "" -#: src/ch00/interacting/factory.md:1 +#: src/getting-started/interacting/factory.md:1 msgid "Factory Pattern" msgstr "" -#: src/ch00/interacting/factory.md:3 +#: src/getting-started/interacting/factory.md:3 msgid "" "The factory pattern is a well known pattern in object oriented programming. " "It provides an abstraction on how to instantiate a class. " msgstr "" -#: src/ch00/interacting/factory.md:5 +#: src/getting-started/interacting/factory.md:5 msgid "" "In the case of smart contracts, we can use this pattern by defining a " "factory contract that have the sole responsibility of creating and managing " "other contracts." msgstr "" -#: src/ch00/interacting/factory.md:7 +#: src/getting-started/interacting/factory.md:7 msgid "Class hash and contract instance" msgstr "" -#: src/ch00/interacting/factory.md:9 +#: src/getting-started/interacting/factory.md:9 msgid "" "In Starknet, there's a separation between contract's classes and instances. " "A contract class serves as a blueprint, defined by the underling Cairo " @@ -1169,162 +1169,162 @@ msgid "" "network, you first need to declare it." msgstr "" -#: src/ch00/interacting/factory.md:11 +#: src/getting-started/interacting/factory.md:11 msgid "" "When deploying a contract, you need to specify the class hash of the " "contract you want to deploy. Each instance of a contract has their own " "storage regardless of the class hash." msgstr "" -#: src/ch00/interacting/factory.md:13 +#: src/getting-started/interacting/factory.md:13 msgid "" "Using the factory pattern, we can deploy multiple instances of the same " "contract class and handle upgrades easily." msgstr "" -#: src/ch00/interacting/factory.md:15 +#: src/getting-started/interacting/factory.md:15 msgid "Minimal example" msgstr "" -#: src/ch00/interacting/factory.md:17 +#: src/getting-started/interacting/factory.md:17 msgid "" "Here's a minimal example of a factory contract that deploy the " "`SimpleCounter` contract:" msgstr "" -#: src/ch00/interacting/factory.md:24 +#: src/getting-started/interacting/factory.md:24 msgid "/// Create a new counter contract from stored arguments\n" msgstr "" -#: src/ch00/interacting/factory.md:27 +#: src/getting-started/interacting/factory.md:27 msgid "/// Create a new counter contract from the given arguments\n" msgstr "" -#: src/ch00/interacting/factory.md:30 +#: src/getting-started/interacting/factory.md:30 msgid "/// Update the argument\n" msgstr "" -#: src/ch00/interacting/factory.md:33 +#: src/getting-started/interacting/factory.md:33 msgid "" "/// Update the class hash of the Counter contract to deploy when creating a " "new counter\n" msgstr "" -#: src/ch00/interacting/factory.md:44 +#: src/getting-started/interacting/factory.md:44 msgid "/// Store the constructor arguments of the contract to deploy\n" msgstr "" -#: src/ch00/interacting/factory.md:46 +#: src/getting-started/interacting/factory.md:46 msgid "/// Store the class hash of the contract to deploy\n" msgstr "" -#: src/ch00/interacting/factory.md:59 +#: src/getting-started/interacting/factory.md:59 msgid "// Contructor arguments\n" msgstr "" -#: src/ch00/interacting/factory.md:62 +#: src/getting-started/interacting/factory.md:62 msgid "// Contract deployment\n" msgstr "" -#: src/ch00/interacting/factory.md:88 +#: src/getting-started/interacting/factory.md:88 msgid "" "This factory can be used to deploy multiple instances of the `SimpleCounter` " "contract by calling the `create_counter` and `create_counter_at` functions." msgstr "" -#: src/ch00/interacting/factory.md:90 +#: src/getting-started/interacting/factory.md:90 msgid "" "The `SimpleCounter` class hash is stored inside the factory, and can be " "upgraded with the `update_counter_class_hash` function which allows to reuse " "the same factory contract when the `SimpleCounter` contract is upgraded." msgstr "" -#: src/ch00/interacting/factory.md:92 +#: src/getting-started/interacting/factory.md:92 msgid "" "This minimal example lacks several useful features such as access control, " "tracking of deployed contracts, events, ..." msgstr "" -#: src/ch00/testing/contract-testing.md:1 +#: src/getting-started/testing/contract-testing.md:1 msgid "Contract Testing" msgstr "" -#: src/ch00/testing/contract-testing.md:3 +#: src/getting-started/testing/contract-testing.md:3 msgid "" "Testing plays a crucial role in software development, especially for smart " "contracts. In this section, we'll guide you through the basics of testing a " "smart contract on Starknet with `scarb`." msgstr "" -#: src/ch00/testing/contract-testing.md:5 +#: src/getting-started/testing/contract-testing.md:5 msgid "Let's start with a simple smart contract as an example:" msgstr "" -#: src/ch00/testing/contract-testing.md:50 +#: src/getting-started/testing/contract-testing.md:50 msgid "Now, take a look at the tests for this contract:" msgstr "" -#: src/ch00/testing/contract-testing.md:54 +#: src/getting-started/testing/contract-testing.md:54 msgid "" "// Import the interface and dispatcher to be able to interact with the " "contract.\n" msgstr "" -#: src/ch00/testing/contract-testing.md:59 +#: src/getting-started/testing/contract-testing.md:59 msgid "// Import the deploy syscall to be able to deploy the contract.\n" msgstr "" -#: src/ch00/testing/contract-testing.md:66 +#: src/getting-started/testing/contract-testing.md:66 msgid "// Use starknet test utils to fake the transaction context.\n" msgstr "" -#: src/ch00/testing/contract-testing.md:69 +#: src/getting-started/testing/contract-testing.md:69 msgid "// Deploy the contract and return its dispatcher.\n" msgstr "" -#: src/ch00/testing/contract-testing.md:71 +#: src/getting-started/testing/contract-testing.md:71 msgid "// Set up constructor arguments.\n" msgstr "" -#: src/ch00/testing/contract-testing.md:75 +#: src/getting-started/testing/contract-testing.md:75 msgid "// Declare and deploy\n" msgstr "" -#: src/ch00/testing/contract-testing.md:81 +#: src/getting-started/testing/contract-testing.md:81 msgid "" "// Return the dispatcher.\n" " // The dispatcher allows to interact with the contract based on its " "interface.\n" msgstr "" -#: src/ch00/testing/contract-testing.md:99 +#: src/getting-started/testing/contract-testing.md:99 msgid "// Fake the caller address to address 1\n" msgstr "" -#: src/ch00/testing/contract-testing.md:106 +#: src/getting-started/testing/contract-testing.md:106 msgid "// Fake the contract address to address 1\n" msgstr "" -#: src/ch00/testing/contract-testing.md:132 +#: src/getting-started/testing/contract-testing.md:132 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch00-getting-started/testing/src/lib.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/getting-started-getting-started/testing/src/lib.cairo)." msgstr "" -#: src/ch00/testing/contract-testing.md:134 +#: src/getting-started/testing/contract-testing.md:134 msgid "" "To define our test, we use scarb, which allows us to create a separate " "module guarded with `#[cfg(test)]`. This ensures that the test module is " "only compiled when running tests using `scarb test`." msgstr "" -#: src/ch00/testing/contract-testing.md:136 +#: src/getting-started/testing/contract-testing.md:136 msgid "" "Each test is defined as a function with the `#[test]` attribute. You can " "also check if a test panics using the `#[should_panic]` attribute." msgstr "" -#: src/ch00/testing/contract-testing.md:138 +#: src/getting-started/testing/contract-testing.md:138 msgid "" "As we are in the context of a smart contract, it's essential to set up the " "gas limit. You do this by using the `#[available_gas(X)]` attribute to " @@ -1332,85 +1332,85 @@ msgid "" "your contract's features stay under a certain gas limit!" msgstr "" -#: src/ch00/testing/contract-testing.md:140 +#: src/getting-started/testing/contract-testing.md:140 msgid "Note: The term \"gas\" here refers to Sierra gas, not L1 gas" msgstr "" -#: src/ch00/testing/contract-testing.md:142 +#: src/getting-started/testing/contract-testing.md:142 msgid "Now, let's move on to the testing process:" msgstr "" -#: src/ch00/testing/contract-testing.md:143 +#: src/getting-started/testing/contract-testing.md:143 msgid "Use the `deploy` function logic to declare and deploy your contract." msgstr "" -#: src/ch00/testing/contract-testing.md:144 +#: src/getting-started/testing/contract-testing.md:144 msgid "" "Use `assert` to verify that the contract behaves as expected in the given " "context." msgstr "" -#: src/ch00/testing/contract-testing.md:146 +#: src/getting-started/testing/contract-testing.md:146 msgid "" "To make testing more convenient, the `testing` module of the corelib " "provides some helpful functions:" msgstr "" -#: src/ch00/testing/contract-testing.md:147 +#: src/getting-started/testing/contract-testing.md:147 msgid "`set_caller_address(address: ContractAddress)`" msgstr "" -#: src/ch00/testing/contract-testing.md:148 +#: src/getting-started/testing/contract-testing.md:148 msgid "`set_contract_address(address: ContractAddress)`" msgstr "" -#: src/ch00/testing/contract-testing.md:149 +#: src/getting-started/testing/contract-testing.md:149 msgid "`set_block_number(block_number: u64)`" msgstr "" -#: src/ch00/testing/contract-testing.md:150 +#: src/getting-started/testing/contract-testing.md:150 msgid "`set_block_timestamp(block_timestamp: u64)`" msgstr "" -#: src/ch00/testing/contract-testing.md:151 +#: src/getting-started/testing/contract-testing.md:151 msgid "`set_account_contract_address(address: ContractAddress)`" msgstr "" -#: src/ch00/testing/contract-testing.md:152 +#: src/getting-started/testing/contract-testing.md:152 msgid "`set_max_fee(fee: u128)`" msgstr "" -#: src/ch00/testing/contract-testing.md:154 +#: src/getting-started/testing/contract-testing.md:154 msgid "" "You may also need the `info` module from the corelib, which allows you to " "access information about the current transaction context:" msgstr "" -#: src/ch00/testing/contract-testing.md:155 +#: src/getting-started/testing/contract-testing.md:155 msgid "`get_caller_address() -> ContractAddress`" msgstr "" -#: src/ch00/testing/contract-testing.md:156 +#: src/getting-started/testing/contract-testing.md:156 msgid "`get_contract_address() -> ContractAddress`" msgstr "" -#: src/ch00/testing/contract-testing.md:157 +#: src/getting-started/testing/contract-testing.md:157 msgid "`get_block_info() -> Box`" msgstr "" -#: src/ch00/testing/contract-testing.md:158 +#: src/getting-started/testing/contract-testing.md:158 msgid "`get_tx_info() -> Box`" msgstr "" -#: src/ch00/testing/contract-testing.md:159 +#: src/getting-started/testing/contract-testing.md:159 msgid "`get_block_timestamp() -> u64`" msgstr "" -#: src/ch00/testing/contract-testing.md:160 +#: src/getting-started/testing/contract-testing.md:160 msgid "`get_block_number() -> u64`" msgstr "" -#: src/ch00/testing/contract-testing.md:163 +#: src/getting-started/testing/contract-testing.md:163 msgid "" "You can found the full list of functions in the [Starknet Corelib " "repo](https://github.com/starkware-libs/cairo/tree/main/corelib/src/starknet). " @@ -1419,18 +1419,18 @@ msgid "" "8](https://book.cairo-lang.org/ch08-01-how-to-write-tests.html)." msgstr "" -#: src/ch00/testing/contract-testing.md:166 +#: src/getting-started/testing/contract-testing.md:166 msgid "Starknet Foundry" msgstr "" -#: src/ch00/testing/contract-testing.md:170 +#: src/getting-started/testing/contract-testing.md:170 msgid "" "Starknet Foundry is a powerful toolkit for developing smart contracts on " "Starknet. It offers support for testing Starknet smart contracts on top of " "`scarb` with the `Forge` tool." msgstr "" -#: src/ch00/testing/contract-testing.md:172 +#: src/getting-started/testing/contract-testing.md:172 msgid "" "Testing with `snforge` is similar to the process we just described but " "simplified. Moreover, additional features are on the way, including " @@ -1438,81 +1438,81 @@ msgid "" "Starknet Foundry and incorporating it into your projects." msgstr "" -#: src/ch00/testing/contract-testing.md:174 +#: src/getting-started/testing/contract-testing.md:174 msgid "" "For more detailed information about testing contracts with Starknet Foundry, " "check out the [Starknet Foundry Book - Testing " "Contracts](https://foundry-rs.github.io/starknet-foundry/testing/contracts.html)." msgstr "" -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:1 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:1 msgid "Cairo Cheatsheet" msgstr "" -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:3 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:3 msgid "" "This chapter aims to provide a quick reference for the most common Cairo " "constructs." msgstr "" -#: src/ch00/cairo_cheatsheet/felt.md:1 +#: src/getting-started/cairo_cheatsheet/felt.md:1 msgid "Felt252" msgstr "" -#: src/ch00/cairo_cheatsheet/felt.md:3 +#: src/getting-started/cairo_cheatsheet/felt.md:3 msgid "" "Felt252 is a fundamental data type in Cairo from which all other data types " "are derived. Felt252 can also be used to store short-string representations " "with a maximum length of 31 characters." msgstr "" -#: src/ch00/cairo_cheatsheet/felt.md:6 src/ch00/cairo_cheatsheet/arrays.md:20 -#: src/ch02/hash-solidity-compatible.md:5 +#: src/getting-started/cairo_cheatsheet/felt.md:6 src/getting-started/cairo_cheatsheet/arrays.md:20 +#: src/advanced-concepts/hash-solidity-compatible.md:5 msgid "For example:" msgstr "" -#: src/ch00/cairo_cheatsheet/mapping.md:1 +#: src/getting-started/cairo_cheatsheet/mapping.md:1 msgid "Mapping" msgstr "" -#: src/ch00/cairo_cheatsheet/mapping.md:3 +#: src/getting-started/cairo_cheatsheet/mapping.md:3 msgid "The `LegacyMap` type can be used to represent a collection of key-value." msgstr "" -#: src/ch00/cairo_cheatsheet/mapping.md:51 +#: src/getting-started/cairo_cheatsheet/mapping.md:51 msgid "" "// for a 2D mapping its important to take note of the amount of brackets " "being used.\n" msgstr "" -#: src/ch00/cairo_cheatsheet/arrays.md:3 +#: src/getting-started/cairo_cheatsheet/arrays.md:3 msgid "" "Arrays are collections of elements of the same type. The possible operations " "on arrays are defined with the `array::ArrayTrait` of the corelib:" msgstr "" -#: src/ch00/cairo_cheatsheet/arrays.md:37 +#: src/getting-started/cairo_cheatsheet/arrays.md:37 msgid "// Returns true if an array is empty, then false if it isn't.\n" msgstr "" -#: src/ch00/cairo_cheatsheet/loop.md:3 +#: src/getting-started/cairo_cheatsheet/loop.md:3 msgid "" "A loop specifies a block of code that will run repetitively until a halting " "condition is encountered. For example:" msgstr "" -#: src/ch00/cairo_cheatsheet/loop.md:9 +#: src/getting-started/cairo_cheatsheet/loop.md:9 msgid "// Same as ~ while (i < 10) arr.append(i++);\n" msgstr "" -#: src/ch00/cairo_cheatsheet/match.md:3 +#: src/getting-started/cairo_cheatsheet/match.md:3 msgid "" "The `match` expression in Cairo allows us to control the flow of our code " "by comparing a `felt252` data type or an enum against various patterns and then " "running specific code based on the pattern that matches. For example:" msgstr "" -#: src/ch00/cairo_cheatsheet/tuples.md:3 +#: src/getting-started/cairo_cheatsheet/tuples.md:3 msgid "" "Tuples is a data type to group a fixed number of items of potentially " "different types into a single compound structure. Unlike arrays, tuples have " @@ -1520,31 +1520,31 @@ msgid "" "created, its size cannot change. For example:" msgstr "" -#: src/ch00/cairo_cheatsheet/tuples.md:7 +#: src/getting-started/cairo_cheatsheet/tuples.md:7 msgid "\"0x000\"" msgstr "" -#: src/ch00/cairo_cheatsheet/tuples.md:11 +#: src/getting-started/cairo_cheatsheet/tuples.md:11 msgid "// Create tuple\n" msgstr "" -#: src/ch00/cairo_cheatsheet/tuples.md:14 +#: src/getting-started/cairo_cheatsheet/tuples.md:14 msgid "// Access tuple\n" msgstr "" -#: src/ch00/cairo_cheatsheet/struct.md:3 +#: src/getting-started/cairo_cheatsheet/struct.md:3 msgid "" "A struct is a data type similar to tuple. Like tuples they can be used to " "hold data of different types. For example:" msgstr "" -#: src/ch00/cairo_cheatsheet/struct.md:7 +#: src/getting-started/cairo_cheatsheet/struct.md:7 msgid "" "// With Store, you can store Data's structs in the storage part of " "contracts.\n" msgstr "" -#: src/ch00/cairo_cheatsheet/type_casting.md:3 +#: src/getting-started/cairo_cheatsheet/type_casting.md:3 msgid "" "Cairo supports the conversion from one scalar types to another by using the " "into and try_into methods. `traits::Into` is used for conversion from a " @@ -1553,20 +1553,20 @@ msgid "" "example:" msgstr "" -#: src/ch00/cairo_cheatsheet/type_casting.md:11 +#: src/getting-started/cairo_cheatsheet/type_casting.md:11 msgid "" "// Since a u32 might not fit in a u8 and a u16, we need to use try_into,\n" " // then unwrap the Option type thats returned.\n" msgstr "" -#: src/ch00/cairo_cheatsheet/type_casting.md:16 +#: src/getting-started/cairo_cheatsheet/type_casting.md:16 msgid "" "// since new_u32 is the of the same type (u32) as rand_number, we can " "directly assign them,\n" " // or use the .into() method.\n" msgstr "" -#: src/ch00/cairo_cheatsheet/type_casting.md:20 +#: src/getting-started/cairo_cheatsheet/type_casting.md:20 msgid "" "// When typecasting from a smaller size to an equal or larger size we use " "the .into() method.\n" @@ -1574,15 +1574,15 @@ msgid "" "into them.\n" msgstr "" -#: src/ch00/cairo_cheatsheet/type_casting.md:25 +#: src/getting-started/cairo_cheatsheet/type_casting.md:25 msgid "// Since a felt252 is smaller than a u256, we can use the into() method\n" msgstr "" -#: src/ch00/cairo_cheatsheet/type_casting.md:29 +#: src/getting-started/cairo_cheatsheet/type_casting.md:29 msgid "// Note: usize is smaller than felt252, so we use try_into\n" msgstr "" -#: src/ch01/upgradeable_contract.md:3 +#: src/applications/upgradeable_contract.md:3 msgid "" "In Starknet, contracts are divided into two parts: contract classes and " "contract instances. This division follows a similar concept used in " @@ -1590,7 +1590,7 @@ msgid "" "definition and implementation of objects." msgstr "" -#: src/ch01/upgradeable_contract.md:8 +#: src/applications/upgradeable_contract.md:8 msgid "" "A contract class is the definition of a contract: it specifies how the " "contract behaves. It contains essential information like the Cairo byte " @@ -1598,7 +1598,7 @@ msgid "" "semantics unambiguously." msgstr "" -#: src/ch01/upgradeable_contract.md:13 +#: src/applications/upgradeable_contract.md:13 msgid "" "To identify different contract classes, Starknet assigns a unique identifier " "to each class: the class hash. A contract instance is a deployed contract " @@ -1606,14 +1606,14 @@ msgid "" "an object in languages like Java." msgstr "" -#: src/ch01/upgradeable_contract.md:18 +#: src/applications/upgradeable_contract.md:18 msgid "" "Each class is identified by its class hash, which is analogous to a class " "name in an object-oriented programming language. A contract instance is a " "deployed contract corresponding to a class." msgstr "" -#: src/ch01/upgradeable_contract.md:20 +#: src/applications/upgradeable_contract.md:20 msgid "" "You can upgrade a deployed contract to a newer version by calling the " "`replace_class_syscall` function. By using this function, you can update the " @@ -1622,7 +1622,7 @@ msgid "" "the data stored in the contract will remain the same." msgstr "" -#: src/ch01/upgradeable_contract.md:22 +#: src/applications/upgradeable_contract.md:22 msgid "" "To illustrate this concept, let's consider an example with two contracts: " "`UpgradeableContract_V0`, and `UpgradeableContract_V1`. Start by deploying " @@ -1634,52 +1634,52 @@ msgid "" "the V1 version." msgstr "" -#: src/ch01/upgradeable_contract.md:68 +#: src/applications/upgradeable_contract.md:68 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x005300003ade5d10447d941a42d48b7141074cd8bade2b16520684896a5090ea) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch01-applications/upgradeable_contract/src/upgradeable_contract_v0.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/applications-applications/upgradeable_contract/src/upgradeable_contract_v0.cairo)." msgstr "" -#: src/ch01/upgradeable_contract.md:114 +#: src/applications/upgradeable_contract.md:114 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x017c86152badd1d665b9836571bd6b0a484f028748aa13d9b2d5d9c9192fafc6) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch01-applications/upgradeable_contract/src/upgradeable_contract_v1.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/applications-applications/upgradeable_contract/src/upgradeable_contract_v1.cairo)." msgstr "" -#: src/ch01/simple_vault.md:1 +#: src/applications/simple_vault.md:1 msgid "Simple Defi Vault" msgstr "" -#: src/ch01/simple_vault.md:3 +#: src/applications/simple_vault.md:3 msgid "" "This is the Cairo adaptation of the [Solidity by example " "Vault](https://solidity-by-example.org/defi/vault/). Here's how it works:" msgstr "" -#: src/ch01/simple_vault.md:6 +#: src/applications/simple_vault.md:6 msgid "" "When a user deposits a token, the contract calculates the amount of shares " "to mint." msgstr "" -#: src/ch01/simple_vault.md:8 +#: src/applications/simple_vault.md:8 msgid "" "When a user withdraws, the contract burns their shares, calculates the " "yield, and withdraw both the yield and the initial amount of token deposited." msgstr "" -#: src/ch01/simple_vault.md:12 +#: src/applications/simple_vault.md:12 msgid "" "// In order to make contract calls within our Vault,\n" "// we need to have the interface of the remote ERC20 contract defined to " "import the Dispatcher.\n" msgstr "" -#: src/ch01/simple_vault.md:68 +#: src/applications/simple_vault.md:68 msgid "" "// a = amount\n" " // B = balance of token before deposit\n" @@ -1691,7 +1691,7 @@ msgid "" " // s = aT / B\n" msgstr "" -#: src/ch01/simple_vault.md:92 +#: src/applications/simple_vault.md:92 msgid "" "// a = amount\n" " // B = balance of token before withdraw\n" @@ -1703,24 +1703,24 @@ msgid "" " // a = sB / T\n" msgstr "" -#: src/ch01/simple_vault.md:113 +#: src/applications/simple_vault.md:113 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch01-applications/simple_vault/src/simple_vault.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/applications-applications/simple_vault/src/simple_vault.cairo)." msgstr "" -#: src/ch01/erc20.md:3 +#: src/applications/erc20.md:3 msgid "" "Contracts that follow the [ERC20 " "Standard](https://eips.ethereum.org/EIPS/eip-20) are called ERC20 tokens. " "They are used to represent fungible assets." msgstr "" -#: src/ch01/erc20.md:5 +#: src/applications/erc20.md:5 msgid "To create an ERC20 conctract, it must implement the following interface:" msgstr "" -#: src/ch01/erc20.md:33 +#: src/applications/erc20.md:33 msgid "" "In Starknet, function names should be written in _snake_case_. This is not " "the case in Solidity, where function names are written in _camelCase_. The " @@ -1728,44 +1728,44 @@ msgid "" "ERC20 interface." msgstr "" -#: src/ch01/erc20.md:36 +#: src/applications/erc20.md:36 msgid "Here's an implementation of the ERC20 interface in Cairo:" msgstr "" -#: src/ch01/erc20.md:207 +#: src/applications/erc20.md:207 msgid "// What can go wrong here?\n" msgstr "" -#: src/ch01/erc20.md:224 +#: src/applications/erc20.md:224 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch01-applications/erc20/src/token.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/applications-applications/erc20/src/token.cairo)." msgstr "" -#: src/ch01/erc20.md:226 +#: src/applications/erc20.md:226 msgid "" "There's several other implementations, such as the [Open " "Zeppelin](https://docs.openzeppelin.com/contracts-cairo/0.7.0/erc20) or the " "[Cairo By Example](https://cairo-by-example.com/examples/erc20/) ones." msgstr "" -#: src/ch01/constant-product-amm.md:3 +#: src/applications/constant-product-amm.md:3 msgid "" "This is the Cairo adaptation of the [Solidity by example Constant Product " "AMM](https://solidity-by-example.org/defi/constant-product-amm/)." msgstr "" -#: src/ch01/constant-product-amm.md:32 +#: src/applications/constant-product-amm.md:32 msgid "" "// Fee 0 - 1000 (0% - 100%, 1 decimal places)\n" " // E.g. 3 = 0.3%\n" msgstr "" -#: src/ch01/constant-product-amm.md:41 +#: src/applications/constant-product-amm.md:41 msgid "// assert(fee <= 1000, 'fee > 1000');\n" msgstr "" -#: src/ch01/constant-product-amm.md:107 +#: src/applications/constant-product-amm.md:107 msgid "" "// How much dy for dx?\n" " // xy = k\n" @@ -1777,7 +1777,7 @@ msgid "" " // ydx / (x + dx) = dy\n" msgstr "" -#: src/ch01/constant-product-amm.md:135 +#: src/applications/constant-product-amm.md:135 msgid "" "// How much dx, dy to add?\n" " //\n" @@ -1794,7 +1794,7 @@ msgid "" " // dy = y / x * dx\n" msgstr "" -#: src/ch01/constant-product-amm.md:154 +#: src/applications/constant-product-amm.md:154 msgid "" "// How much shares to mint?\n" " //\n" @@ -1815,7 +1815,7 @@ msgid "" " // (L1 - L0) * T / L0 = s\n" msgstr "" -#: src/ch01/constant-product-amm.md:171 +#: src/applications/constant-product-amm.md:171 msgid "" "// Claim\n" " // (L1 - L0) / L0 = dx / x = dy / y\n" @@ -1851,7 +1851,7 @@ msgid "" " // (L1 - L0) / L0 = dx / x = dy / y\n" msgstr "" -#: src/ch01/constant-product-amm.md:221 +#: src/applications/constant-product-amm.md:221 msgid "" "// Claim\n" " // dx, dy = amount of liquidity to remove\n" @@ -1886,19 +1886,19 @@ msgid "" " // dy = s / T * y\n" msgstr "" -#: src/ch01/constant-product-amm.md:253 +#: src/applications/constant-product-amm.md:253 msgid "" "// bal0 >= reserve0\n" " // bal1 >= reserve1\n" msgstr "" -#: src/ch01/constant-product-amm.md:274 +#: src/applications/constant-product-amm.md:274 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch01-applications/constant_product_amm/src/constant_product_amm.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/applications-applications/constant_product_amm/src/constant_product_amm.cairo)." msgstr "" -#: src/ch02/write_to_any_slot.md:3 +#: src/advanced-concepts/write_to_any_slot.md:3 msgid "" "On Starknet, a contract's storage is a map with 2^251 slots, where each slot " "is a felt which is initialized to 0. The address of storage variables is " @@ -1908,7 +1908,7 @@ msgid "" "functions." msgstr "" -#: src/ch02/write_to_any_slot.md:6 +#: src/advanced-concepts/write_to_any_slot.md:6 msgid "" "Nevertheless, we can use the `storage_write_syscall` and " "`storage_read_syscall` syscalls, to write to and read from any storage slot. " @@ -1918,7 +1918,7 @@ msgid "" "accessible." msgstr "" -#: src/ch02/write_to_any_slot.md:9 +#: src/advanced-concepts/write_to_any_slot.md:9 msgid "" "In the following example, we use the Poseidon hash function to compute the " "address of a storage variable. Poseidon is a ZK-friendly hash function that " @@ -1927,21 +1927,21 @@ msgid "" "syscalls to interact with it." msgstr "" -#: src/ch02/write_to_any_slot.md:49 +#: src/advanced-concepts/write_to_any_slot.md:49 msgid "" "// By taking the 250 least significant bits of the hash output, we get a " "valid 250bits storage address.\n" msgstr "" -#: src/ch02/write_to_any_slot.md:56 +#: src/advanced-concepts/write_to_any_slot.md:56 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x033943CB781A4E63C9dcE0A1A09eAa3b617AA43CC61637C08c043a67f3fe0087) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch02-advanced-concepts/write_to_any_slot/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts-advanced-concepts/write_to_any_slot/src/contract.cairo)." msgstr "" -#: src/ch02/storing_arrays.md:3 +#: src/advanced-concepts/storing_arrays.md:3 msgid "" "On Starknet, complex values (e.g., tuples or structs), are stored in a " "continuous segment starting from the address of the storage variable. There " @@ -1953,7 +1953,7 @@ msgid "" "implementation of the `Store` trait for the type of array you wish to store." msgstr "" -#: src/ch02/storing_arrays.md:5 +#: src/advanced-concepts/storing_arrays.md:5 msgid "" "Note: While storing arrays in storage is possible, it is not always " "recommended, as the read and write operations can get very costly. For " @@ -1963,54 +1963,54 @@ msgid "" "use a `LegacyMap` and store the length in another variable instead." msgstr "" -#: src/ch02/storing_arrays.md:7 +#: src/advanced-concepts/storing_arrays.md:7 msgid "" "The following example demonstrates how to write a simple implementation of " "the `StorageAccess` trait for the `Array` type, allowing us to " "store arrays of up to 255 `felt252` elements." msgstr "" -#: src/ch02/storing_arrays.md:26 +#: src/advanced-concepts/storing_arrays.md:26 msgid "" "// Read the stored array's length. If the length is superior to 255, the " "read will fail.\n" msgstr "" -#: src/ch02/storing_arrays.md:31 +#: src/advanced-concepts/storing_arrays.md:31 msgid "// Sequentially read all stored elements and append them to the array.\n" msgstr "" -#: src/ch02/storing_arrays.md:43 +#: src/advanced-concepts/storing_arrays.md:43 msgid "// Return the array.\n" msgstr "" -#: src/ch02/storing_arrays.md:50 +#: src/advanced-concepts/storing_arrays.md:50 msgid "// // Store the length of the array in the first storage slot.\n" msgstr "" -#: src/ch02/storing_arrays.md:55 +#: src/advanced-concepts/storing_arrays.md:55 msgid "// Store the array elements sequentially\n" msgstr "" -#: src/ch02/storing_arrays.md:73 +#: src/advanced-concepts/storing_arrays.md:73 msgid "" "You can then import this implementation in your contract and use it to store " "arrays in storage:" msgstr "" -#: src/ch02/storing_arrays.md:103 +#: src/advanced-concepts/storing_arrays.md:103 msgid "" "Visit contract on " "[Voyager](https://goerli.voyager.online/contract/0x008F8069a3Fcd7691Db46Dc3b6F9D2C0436f9200E861330957Fd780A3595da86) " "or play with it in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch02-advanced-concepts/storing_arrays/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts-advanced-concepts/storing_arrays/src/contract.cairo)." msgstr "" -#: src/ch02/struct-mapping-key.md:1 +#: src/advanced-concepts/struct-mapping-key.md:1 msgid "Structs as mapping keys" msgstr "" -#: src/ch02/struct-mapping-key.md:3 +#: src/advanced-concepts/struct-mapping-key.md:3 msgid "" "In order to use structs as mapping keys, you can use `#[derive(Hash)]` on " "the struct definition. This will automatically generate a hash function for " @@ -2018,7 +2018,7 @@ msgid "" "`LegacyMap`." msgstr "" -#: src/ch02/struct-mapping-key.md:5 +#: src/advanced-concepts/struct-mapping-key.md:5 msgid "" "Consider the following example in which we would like to use an object of " "type `Pet` as a key in a `LegacyMap`. The `Pet` struct has three fields: " @@ -2026,13 +2026,13 @@ msgid "" "fields uniquely identifies a pet." msgstr "" -#: src/ch02/struct-mapping-key.md:45 +#: src/advanced-concepts/struct-mapping-key.md:45 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch02-advanced-concepts/struct_as_mapping_key/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts-advanced-concepts/struct_as_mapping_key/src/contract.cairo)." msgstr "" -#: src/ch02/hash-solidity-compatible.md:3 +#: src/advanced-concepts/hash-solidity-compatible.md:3 msgid "" "This contract demonstrates Keccak hashing in Cairo to match Solidity's " "keccak256. While both use Keccak, their endianness differs: Cairo is " @@ -2041,40 +2041,40 @@ msgid "" "bytes of the result with `u128_byte_reverse`." msgstr "" -#: src/ch02/hash-solidity-compatible.md:27 +#: src/advanced-concepts/hash-solidity-compatible.md:27 msgid "// Split the hashed value into two 128-bit segments\n" msgstr "" -#: src/ch02/hash-solidity-compatible.md:31 +#: src/advanced-concepts/hash-solidity-compatible.md:31 msgid "// Reverse each 128-bit segment\n" msgstr "" -#: src/ch02/hash-solidity-compatible.md:35 +#: src/advanced-concepts/hash-solidity-compatible.md:35 msgid "// Reverse merge the reversed segments back into a u256 value\n" msgstr "" -#: src/ch02/hash-solidity-compatible.md:44 +#: src/advanced-concepts/hash-solidity-compatible.md:44 msgid "" "Play with the contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch02-advanced-concepts/hash_solidity_compatible/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts-advanced-concepts/hash_solidity_compatible/src/contract.cairo)." msgstr "" -#: src/ch02/optimisations/optimisations.md:3 +#: src/advanced-concepts/optimisations/optimisations.md:3 msgid "A collection of optimisation patterns to save gas and steps." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:1 +#: src/advanced-concepts/optimisations/store_using_packing.md:1 msgid "Storage optimisation" msgstr "" -#: src/ch02/optimisations/store_using_packing.md:3 +#: src/advanced-concepts/optimisations/store_using_packing.md:3 msgid "" "A smart contract has a limited amount of **storage slots**. Each slot can " "store a single `felt252` value. Writing to a storage slot has a cost, so we " "want to use as few storage slots as possible." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:6 +#: src/advanced-concepts/optimisations/store_using_packing.md:6 msgid "" "In Cairo, every type is derived from the `felt252` type, which uses 252 bits " "to store a value. This design is quite simple, but it does have a drawback: " @@ -2082,11 +2082,11 @@ msgid "" "we need to use an entire slot, even though we only need 8 bits." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:9 +#: src/advanced-concepts/optimisations/store_using_packing.md:9 msgid "Packing" msgstr "" -#: src/ch02/optimisations/store_using_packing.md:11 +#: src/advanced-concepts/optimisations/store_using_packing.md:11 msgid "" "When storing multiple values, we can use a technique called **packing**. " "Packing is a technique that allows us to store multiple values in a single " @@ -2094,7 +2094,7 @@ msgid "" "multiple values." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:13 +#: src/advanced-concepts/optimisations/store_using_packing.md:13 msgid "" "For example, if we want to store two `u8` values, we can use the first 8 " "bits of the felt value to store the first `u8` value, and the last 8 bits to " @@ -2102,13 +2102,13 @@ msgid "" "single felt value." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:15 +#: src/advanced-concepts/optimisations/store_using_packing.md:15 msgid "" "Cairo provides a built-in store using packing that you can use with the " "`StorePacking` trait." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:24 +#: src/advanced-concepts/optimisations/store_using_packing.md:24 msgid "" "This allows to store the type `T` by first packing it into the type " "`PackedT` with the `pack` function, and then storing the `PackedT` value " @@ -2117,32 +2117,32 @@ msgid "" "function." msgstr "" -#: src/ch02/optimisations/store_using_packing.md:26 +#: src/advanced-concepts/optimisations/store_using_packing.md:26 msgid "" "Here's an example of storing a `Time` struct with two `u8` values using the " "`StorePacking` trait:" msgstr "" -#: src/ch02/optimisations/store_using_packing.md:75 +#: src/advanced-concepts/optimisations/store_using_packing.md:75 msgid "" "// This will call the pack method of the TimePackable trait\n" " // and store the resulting felt252\n" msgstr "" -#: src/ch02/optimisations/store_using_packing.md:80 +#: src/advanced-concepts/optimisations/store_using_packing.md:80 msgid "" "// This will read the felt252 value from storage\n" " // and return the result of the unpack method of the " "TimePackable trait\n" msgstr "" -#: src/ch02/optimisations/store_using_packing.md:88 +#: src/advanced-concepts/optimisations/store_using_packing.md:88 msgid "" "Play with this contract in " -"[Remix](https://remix.ethereum.org/?#activate=Starknet-cairo1-compiler&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/ch02-advanced-concepts/store_using_packing/src/contract.cairo)." +"[Remix](https://remix.ethereum.org/?#activate=Starknet-cairo1-compiler&url=https://github.com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts-advanced-concepts/store_using_packing/src/contract.cairo)." msgstr "" -#: src/ch02/list.md:3 +#: src/advanced-concepts/list.md:3 msgid "" "By default, there is no list type supported in Cairo, but you can use " "Alexandria. You can refer to the [Alexandria " @@ -2150,35 +2150,35 @@ msgid "" "for more details." msgstr "" -#: src/ch02/list.md:5 +#: src/advanced-concepts/list.md:5 msgid "What is `List`?" msgstr "" -#: src/ch02/list.md:7 +#: src/advanced-concepts/list.md:7 msgid "An ordered sequence of values that can be used in Starknet storage:" msgstr "" -#: src/ch02/list.md:16 +#: src/advanced-concepts/list.md:16 msgid "Interface" msgstr "" -#: src/ch02/list.md:30 +#: src/advanced-concepts/list.md:30 msgid "" "`List` also implements `IndexView` so you can use the familiar bracket " "notation to access its members:" msgstr "" -#: src/ch02/list.md:36 +#: src/advanced-concepts/list.md:36 msgid "" "Note that unlike `get`, using this bracket notation panics when accessing an " "out of bounds index." msgstr "" -#: src/ch02/list.md:38 +#: src/advanced-concepts/list.md:38 msgid "Support for custom types" msgstr "" -#: src/ch02/list.md:40 +#: src/advanced-concepts/list.md:40 msgid "" "`List` supports most of the corelib types out of the box. If you want to " "store a your own custom type in a `List`, it has to implement the `Store` " @@ -2186,21 +2186,21 @@ msgid "" "`#[derive(starknet::Store)]` attribute." msgstr "" -#: src/ch02/list.md:42 +#: src/advanced-concepts/list.md:42 msgid "Caveats" msgstr "" -#: src/ch02/list.md:44 +#: src/advanced-concepts/list.md:44 msgid "There are two idiosyncacies you should be aware of when using `List`" msgstr "" -#: src/ch02/list.md:46 +#: src/advanced-concepts/list.md:46 msgid "" "The `append` operation costs 2 storage writes - one for the value itself and " "another one for updating the List's length" msgstr "" -#: src/ch02/list.md:47 +#: src/advanced-concepts/list.md:47 msgid "" "Due to a compiler limitation, it is not possible to use mutating operations " "with a single inline statement. For example, " @@ -2208,19 +2208,19 @@ msgid "" "steps:" msgstr "" -#: src/ch02/list.md:54 +#: src/advanced-concepts/list.md:54 msgid "Dependencies" msgstr "" -#: src/ch02/list.md:56 +#: src/advanced-concepts/list.md:56 msgid "Update your project dependencies by in the `Scarb.toml` file:" msgstr "" -#: src/ch02/list.md:60 +#: src/advanced-concepts/list.md:60 msgid "\"https://github.com/keep-starknet-strange/alexandria.git\"" msgstr "" -#: src/ch02/list.md:63 +#: src/advanced-concepts/list.md:63 msgid "" "For example, let's use `List` to create a contract that tracks a list of " "amounts and tasks:" diff --git a/po/zh-cn.po b/po/zh-cn.po index 3ff5d71c..5fea78ee 100644 --- a/po/zh-cn.po +++ b/po/zh-cn.po @@ -284,51 +284,51 @@ msgid "" "strange/awesome-starknet)." msgstr "" -#: src/starknet-by-example.md:31 src/ch00/basics/storage.md:34 -#: src/ch00/basics/constructor.md:27 src/ch00/basics/variables.md:126 -#: src/ch00/basics/visibility-mutability.md:75 src/ch00/basics/counter.md:56 -#: src/ch00/basics/mappings.md:46 src/ch00/basics/errors.md:139 -#: src/ch00/basics/events.md:68 src/ch00/basics/storing-custom-types.md:39 -#: src/ch00/basics/custom-types-in-entrypoints.md:39 -#: src/ch00/interacting/interfaces-traits.md:129 -#: src/ch00/interacting/calling_other_contracts.md:69 -#: src/ch00/testing/contract-testing.md:176 src/ch00/cairo_cheatsheet/felt.md:15 -#: src/ch00/cairo_cheatsheet/mapping.md:58 -#: src/ch00/cairo_cheatsheet/arrays.md:42 src/ch00/cairo_cheatsheet/loop.md:23 -#: src/ch00/cairo_cheatsheet/match.md:59 src/ch00/cairo_cheatsheet/tuples.md:18 -#: src/ch00/cairo_cheatsheet/struct.md:15 -#: src/ch00/cairo_cheatsheet/type_casting.md:33 src/components/how_to.md:114 -#: src/ch01/upgradeable_contract.md:117 src/ch01/simple_vault.md:114 -#: src/ch01/erc20.md:228 src/ch01/constant-product-amm.md:275 -#: src/ch02/write_to_any_slot.md:58 src/ch02/storing_arrays.md:105 -#: src/ch02/struct-mapping-key.md:46 src/ch02/hash-solidity-compatible.md:46 -#: src/ch02/optimisations/store_using_packing.md:90 src/ch02/list.md:139 +#: src/starknet-by-example.md:31 src/getting-started/basics/storage.md:34 +#: src/getting-started/basics/constructor.md:27 src/getting-started/basics/variables.md:126 +#: src/getting-started/basics/visibility-mutability.md:75 src/getting-started/basics/counter.md:56 +#: src/getting-started/basics/mappings.md:46 src/getting-started/basics/errors.md:139 +#: src/getting-started/basics/events.md:68 src/getting-started/basics/storing-custom-types.md:39 +#: src/getting-started/basics/custom-types-in-entrypoints.md:39 +#: src/getting-started/interacting/interfaces-traits.md:129 +#: src/getting-started/interacting/calling_other_contracts.md:69 +#: src/getting-started/testing/contract-testing.md:176 src/getting-started/cairo_cheatsheet/felt.md:15 +#: src/getting-started/cairo_cheatsheet/mapping.md:58 +#: src/getting-started/cairo_cheatsheet/arrays.md:42 src/getting-started/cairo_cheatsheet/loop.md:23 +#: src/getting-started/cairo_cheatsheet/match.md:59 src/getting-started/cairo_cheatsheet/tuples.md:18 +#: src/getting-started/cairo_cheatsheet/struct.md:15 +#: src/getting-started/cairo_cheatsheet/type_casting.md:33 src/components/how_to.md:114 +#: src/applications/upgradeable_contract.md:117 src/applications/simple_vault.md:114 +#: src/applications/erc20.md:228 src/applications/constant-product-amm.md:275 +#: src/advanced-concepts/write_to_any_slot.md:58 src/advanced-concepts/storing_arrays.md:105 +#: src/advanced-concepts/struct-mapping-key.md:46 src/advanced-concepts/hash-solidity-compatible.md:46 +#: src/advanced-concepts/optimisations/store_using_packing.md:90 src/advanced-concepts/list.md:139 msgid "
Last change: 2023-12-07
" msgstr "
Last change: 2023-12-07
" -#: src/ch00/basics/introduction.md:1 +#: src/getting-started/basics/introduction.md:1 msgid "# Basics of Smart Contracts in Cairo" msgstr "# Cairo的智能合约基础知识" -#: src/ch00/basics/introduction.md:3 +#: src/getting-started/basics/introduction.md:3 msgid "" "The following chapters will introduce you to Starknet smart contracts and how " "to write them in Cairo." msgstr "以下章节将向你介绍Starknet智能合约以及如何用Cairo编写这些合约。" -#: src/ch00/basics/introduction.md:5 src/ch02/optimisations/optimisations.md:5 +#: src/getting-started/basics/introduction.md:5 src/advanced-concepts/optimisations/optimisations.md:5 msgid "
Last change: 2023-10-12
" msgstr "
Last change: 2023-10-12
" -#: src/ch00/basics/storage.md:1 +#: src/getting-started/basics/storage.md:1 msgid "# Storage" msgstr "# 存储" -#: src/ch00/basics/storage.md:3 +#: src/getting-started/basics/storage.md:3 msgid "Here's the most minimal contract you can write in Cairo:" msgstr "这是您用Cairo能写的最简短的合约:" -#: src/ch00/basics/storage.md:5 +#: src/getting-started/basics/storage.md:5 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -346,7 +346,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/storage.md:13 +#: src/getting-started/basics/storage.md:13 msgid "" "Storage is a struct annoted with `#[storage]`. Every contract must have one " "and only one storage.\n" @@ -356,7 +356,7 @@ msgstr "" "存储是一个结构体,用 `#[storage]`标注。每个合约必须有且仅有一个存储空间。\n" "它是一个键值存储空间,其中每个键都将映射到合约存储空间的存储地址。" -#: src/ch00/basics/storage.md:16 +#: src/getting-started/basics/storage.md:16 msgid "" "You can define [storage variables](./variables.md#storage-variables) in your " "contract, and then use them to store and retrieve data." @@ -364,7 +364,7 @@ msgstr "" "您可以在合约中定义 [存储变量](./variables.md#storage-variables),然后使用它" "们来存储和检索数据。" -#: src/ch00/basics/storage.md:17 +#: src/getting-started/basics/storage.md:17 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -390,7 +390,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/storage.md:29 +#: src/getting-started/basics/storage.md:29 msgid "" "> Actually these two contracts have the same underlying Sierra program.\n" "> From the compiler's perspective, the storage variables don't exist until " @@ -399,16 +399,16 @@ msgstr "" "> 实际上,这两个合约的底层 Sierra 程序是一样的。\n" "> 从编译器的角度来看,存储变量在使用之前是不存在的。" -#: src/ch00/basics/storage.md:32 +#: src/getting-started/basics/storage.md:32 msgid "" "You can also read about [storing custom types](./storing-custom-types.md)" msgstr "您还可以阅读有关 [存储自定义类型](./storing-custom-types.md) 的内容。" -#: src/ch00/basics/constructor.md:1 +#: src/getting-started/basics/constructor.md:1 msgid "# Constructor" msgstr "# 构造函数" -#: src/ch00/basics/constructor.md:3 +#: src/getting-started/basics/constructor.md:3 msgid "" "Constructors are a special type of function that runs only once when " "deploying a contract, and can be used to initialize the state of the " @@ -420,7 +420,7 @@ msgstr "" "你的合约不能有一个以上的构造函数,而且构造函数必须使用 `#[constructor]` 属性注" "释。此外,一个好的做法是将该函数命名为 `constructor`。" -#: src/ch00/basics/constructor.md:5 +#: src/getting-started/basics/constructor.md:5 msgid "" "Here's a simple example that demonstrates how to initialize the state of a " "contract on deployment by defining logic inside a constructor." @@ -428,7 +428,7 @@ msgstr "" "下面是一个简单的示例,演示如何通过在构造函数中定义逻辑,在部署时初始化合约的状" "态。" -#: src/ch00/basics/constructor.md:7 +#: src/getting-started/basics/constructor.md:7 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -470,7 +470,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/constructor.md:25 +#: src/getting-started/basics/constructor.md:25 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x017fd6558e67451dA583d123D77F4e2651E91502D08F8F8432355293b11e1f8F) " @@ -485,15 +485,15 @@ msgstr "" "blob/main/listings/getting-started/constructor/src/constructor.cairo) 中尝试" "它。" -#: src/ch00/basics/variables.md:1 +#: src/getting-started/basics/variables.md:1 msgid "# Variables" msgstr "# 变量" -#: src/ch00/basics/variables.md:3 +#: src/getting-started/basics/variables.md:3 msgid "There are 3 types of variables in Cairo contracts:" msgstr "Cairo合约中有 3 种变量:" -#: src/ch00/basics/variables.md:5 +#: src/getting-started/basics/variables.md:5 msgid "" "- Local\n" " - declared inside a function\n" @@ -515,11 +515,11 @@ msgstr "" " - 提供有关区块链的信息\n" " - 可在任何地方访问,甚至在库函数中" -#: src/ch00/basics/variables.md:15 +#: src/getting-started/basics/variables.md:15 msgid "## Local Variables" msgstr "## 局部变量" -#: src/ch00/basics/variables.md:17 +#: src/getting-started/basics/variables.md:17 msgid "" "Local variables are used and accessed within the scope of a specific function " "or block of code. They are temporary and exist only for the duration of that " @@ -528,7 +528,7 @@ msgstr "" "局部变量在特定函数或代码块的范围内使用和访问。它们是临时的,只在特定函数或代码" "块执行期间存在。" -#: src/ch00/basics/variables.md:19 +#: src/getting-started/basics/variables.md:19 msgid "" "Local variables are stored in memory and are not stored on the blockchain. " "This means they cannot be accessed from one execution to another. Local " @@ -540,11 +540,11 @@ msgstr "" "局部变量可用于存储仅在特定上下文中相关的临时数据。通过为中间值命名,它们还能使" "代码更具可读性。" -#: src/ch00/basics/variables.md:21 +#: src/getting-started/basics/variables.md:21 msgid "Here's a simple example of a contract with only local variables:" msgstr "下面是一个只有局部变量的简单合约示例:" -#: src/ch00/basics/variables.md:23 +#: src/getting-started/basics/variables.md:23 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -608,7 +608,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/variables.md:50 +#: src/getting-started/basics/variables.md:50 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x015B3a10F9689BeD741Ca3C210017BC097122CeF76f3cAA191A20ff8b9b56b96) " @@ -623,11 +623,11 @@ msgstr "" "blob/main/listings/getting-started/variables/src/local_variables.cairo) 中尝试" "它。" -#: src/ch00/basics/variables.md:52 +#: src/getting-started/basics/variables.md:52 msgid "## Storage Variables" msgstr "## 存储用变量" -#: src/ch00/basics/variables.md:54 +#: src/getting-started/basics/variables.md:54 msgid "" "Storage variables are persistent data stored on the blockchain. They can be " "accessed from one execution to another, allowing the contract to remember and " @@ -636,23 +636,23 @@ msgstr "" "存储变量是存储在区块链上的持久数据。它们可以在不同的执行过程中被访问,从而使合" "约能够保存和更新信息。" -#: src/ch00/basics/variables.md:56 +#: src/getting-started/basics/variables.md:56 msgid "" "To write or update a storage variable, you need to interact with the contract " "through an external entrypoint by sending a transaction." msgstr "要写入或更新存储变量,需要通过外部入口点发送交易与合约交互。" -#: src/ch00/basics/variables.md:58 +#: src/getting-started/basics/variables.md:58 msgid "" "On the other hand, you can read state variables, for free, without any " "transaction, simply by interacting with a node." msgstr "另一方面,只需与节点交互,就可以免费读取状态变量,无需发出任何交易。" -#: src/ch00/basics/variables.md:60 +#: src/getting-started/basics/variables.md:60 msgid "Here's a simple example of a contract with one storage variable:" msgstr "下面是一个带有一个存储变量的简单合约示例:" -#: src/ch00/basics/variables.md:62 +#: src/getting-started/basics/variables.md:62 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -720,7 +720,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/variables.md:92 +#: src/getting-started/basics/variables.md:92 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x06eA827B32875483709b785A7F9e846a52776Cd8D42C3fE696218c2624b0DCCa) " @@ -735,11 +735,11 @@ msgstr "" "blob/main/listings/getting-started/variables/src/storage_variables.cairo)中尝" "试它。" -#: src/ch00/basics/variables.md:94 +#: src/getting-started/basics/variables.md:94 msgid "## Global Variables" msgstr "## 全局变量" -#: src/ch00/basics/variables.md:96 +#: src/getting-started/basics/variables.md:96 msgid "" "Global variables are predefined variables that provide information about the " "blockchain and the current execution environment. They can be accessed at any " @@ -748,14 +748,14 @@ msgstr "" "全局变量是预定义变量,可提供有关区块链和当前执行环境的信息。可以随时随地访问它" "们!" -#: src/ch00/basics/variables.md:98 +#: src/getting-started/basics/variables.md:98 msgid "" "In Starknet, you can access global variables by using specific functions " "contained in the starknet core libraries." msgstr "" "在 Starknet 中,您可以通过使用 starknet 核心库中的特定函数来访问全局变量。" -#: src/ch00/basics/variables.md:100 +#: src/getting-started/basics/variables.md:100 msgid "" "For example, the `get_caller_address` function returns the address of the " "caller of the current transaction, and the `get_contract_address` function " @@ -764,7 +764,7 @@ msgstr "" "例如,`get_caller_address`函数返回当前事务的调用者地址,`get_contract_address`" "函数返回当前合约的地址。" -#: src/ch00/basics/variables.md:102 +#: src/getting-started/basics/variables.md:102 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -816,7 +816,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/variables.md:125 +#: src/getting-started/basics/variables.md:125 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x05bD2F3943bd4e030f85678b55b2EC2C1be939e32388530FB20ED967B3Be433F) " @@ -831,19 +831,19 @@ msgstr "" "blob/main/listings/getting-started/variables/src/global_variables.cairo) 中尝" "试它。" -#: src/ch00/basics/visibility-mutability.md:1 +#: src/getting-started/basics/visibility-mutability.md:1 msgid "# Visibility and Mutability" msgstr "# 可见性和可变性" -#: src/ch00/basics/visibility-mutability.md:3 +#: src/getting-started/basics/visibility-mutability.md:3 msgid "## Visibility" msgstr "## 可见性" -#: src/ch00/basics/visibility-mutability.md:5 +#: src/getting-started/basics/visibility-mutability.md:5 msgid "There are two types of functions in Starknet contracts:" msgstr "Starknet合约有两种功能:" -#: src/ch00/basics/visibility-mutability.md:7 +#: src/getting-started/basics/visibility-mutability.md:7 msgid "" "- Functions that are accessible externally and can be called by anyone.\n" "- Functions that are only accessible internally and can only be called by " @@ -852,7 +852,7 @@ msgstr "" "- 外部可访问、任何人都可调用的函数。\n" "- 只能在内部访问的函数,只能被合约中的其他函数调用。" -#: src/ch00/basics/visibility-mutability.md:10 +#: src/getting-started/basics/visibility-mutability.md:10 msgid "" "These functions are also typically divided into two different implementations " "blocks. The first `impl` block for externally accessible functions is " @@ -867,11 +867,11 @@ msgstr "" "调用。第二个用于内部可访问函数的 `impl` 块没有注释任何属性,这意味着该块中的所" "有函数默认都是私有的。" -#: src/ch00/basics/visibility-mutability.md:12 +#: src/getting-started/basics/visibility-mutability.md:12 msgid "## State Mutability" msgstr "## 状态可变性" -#: src/ch00/basics/visibility-mutability.md:14 +#: src/getting-started/basics/visibility-mutability.md:14 msgid "" "Regardless of whether a function is internal or external, it can either " "modify the contract's state or not. When we declare functions that interact " @@ -885,7 +885,7 @@ msgstr "" "我们需要将 `ContractState`添加为函数的第一个参数,明确说明我们正在访问 合约的" "状态。这有两种不同的方法:" -#: src/ch00/basics/visibility-mutability.md:17 +#: src/getting-started/basics/visibility-mutability.md:17 msgid "" "- If we want our function to be able to mutate the state of the contract, we " "pass it by reference like this: `ref self: ContractState`.\n" @@ -897,7 +897,7 @@ msgstr "" "- 如果我们希望我们的函数是只读的,并且不更改合约的状态,我们可以通过快照传递" "它,如下所示:`self:@ContractState`." -#: src/ch00/basics/visibility-mutability.md:20 +#: src/getting-started/basics/visibility-mutability.md:20 msgid "" "Read-only functions, also called view functions, can be directly called " "without making a transaction. You can interact with them directly through a " @@ -909,17 +909,17 @@ msgstr "" "节点与它们交互,读取合约的状态,而且可以自由调用!\n" "而修改合约状态的外部函数则只能通过交易来调用。" -#: src/ch00/basics/visibility-mutability.md:23 +#: src/getting-started/basics/visibility-mutability.md:23 msgid "" "Internal functions can't be called externally, but the same principle applies " "regarding state mutability." msgstr "内部函数不能被外部调用,同样的原则也适用于状态可变性。" -#: src/ch00/basics/visibility-mutability.md:25 +#: src/getting-started/basics/visibility-mutability.md:25 msgid "Let's take a look at a simple example contract to see these in action:" msgstr "让我们通过一个简单的合约示例来了解这些功能:" -#: src/ch00/basics/visibility-mutability.md:27 +#: src/getting-started/basics/visibility-mutability.md:27 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1037,7 +1037,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/visibility-mutability.md:73 +#: src/getting-started/basics/visibility-mutability.md:73 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x0071dE3093AB58053b0292C225aa0eED40293e7694A0042685FF6D813d39889F) " @@ -1051,19 +1051,19 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/visibility/src/visibility.cairo)中尝试它。" -#: src/ch00/basics/counter.md:1 +#: src/getting-started/basics/counter.md:1 msgid "# Simple Counter" msgstr "# 简单计数器" -#: src/ch00/basics/counter.md:3 +#: src/getting-started/basics/counter.md:3 msgid "This is a simple counter contract." msgstr "这是一个简单的计数合约。" -#: src/ch00/basics/counter.md:5 +#: src/getting-started/basics/counter.md:5 msgid "Here's how it works:" msgstr "这个合约是这样工作的:" -#: src/ch00/basics/counter.md:7 +#: src/getting-started/basics/counter.md:7 msgid "" "- The contract has a state variable called 'counter' that is initialized to " "0.\n" @@ -1078,7 +1078,7 @@ msgstr "" "\n" "- 当用户调用 'decrement'时,合约会将计数器递减 1。" -#: src/ch00/basics/counter.md:13 +#: src/getting-started/basics/counter.md:13 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1164,7 +1164,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/counter.md:54 +#: src/getting-started/basics/counter.md:54 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x01664a69Fe701a1df7Bb0ae4A353792d0cf4E27146ee860075cbf6108b1D5718) " @@ -1178,11 +1178,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/counter/src/contracts.cairo) 中尝试它。" -#: src/ch00/basics/mappings.md:1 +#: src/getting-started/basics/mappings.md:1 msgid "# Mappings" msgstr "# 映射" -#: src/ch00/basics/mappings.md:3 +#: src/getting-started/basics/mappings.md:3 msgid "" "Maps are a key-value data structure used to store data within a smart " "contract. In Cairo they are implemented using the `LegacyMap` type. It's " @@ -1193,7 +1193,7 @@ msgstr "" "`LegacyMap` 类型实现。值得注意的是,`LegacyMap`类型只能在合约的 `Storage`结构" "中使用,不能用在其他地方。" -#: src/ch00/basics/mappings.md:5 +#: src/getting-started/basics/mappings.md:5 msgid "" "Here we demonstrate how to use the `LegacyMap` type within a Cairo contract, " "to map between a key of type `ContractAddress` and value of type `felt252`. " @@ -1207,11 +1207,11 @@ msgstr "" "调用 `write()` 方法,传入键和值,写入映射。同样,我们可以通过调用 `read()` 方" "法并输入相关键值来读取与给定键值相关的值。" -#: src/ch00/basics/mappings.md:7 +#: src/getting-started/basics/mappings.md:7 msgid "Some additional notes:" msgstr "一些补充说明:" -#: src/ch00/basics/mappings.md:9 +#: src/getting-started/basics/mappings.md:9 msgid "" "- More complex key-value mappings are possible, for example we could use " "`LegacyMap::<(ContractAddress, ContractAddress), felt252>` to create an " @@ -1233,7 +1233,7 @@ msgstr "" "io/documentation/architecture_and_concepts/Contracts/contract-storage/" "#storage_variables)。" -#: src/ch00/basics/mappings.md:13 +#: src/getting-started/basics/mappings.md:13 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -1303,7 +1303,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/mappings.md:44 +#: src/getting-started/basics/mappings.md:44 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x06214AB4c23Cc545bf2221D465eB83aFb7412779AD498BD48a724B3F645E3505) " @@ -1317,11 +1317,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/mappings/src/mappings.cairo) 中尝试它。" -#: src/ch00/basics/errors.md:1 +#: src/getting-started/basics/errors.md:1 msgid "# Errors" msgstr "# 错误" -#: src/ch00/basics/errors.md:3 +#: src/getting-started/basics/errors.md:3 msgid "" "Errors can be used to handle validation and other conditions that may occur " "during the execution of a smart contract.\n" @@ -1332,11 +1332,11 @@ msgstr "" "如果在执行智能合约调用期间抛出错误,则将停止执行,并恢复在交易期间所做的任何更" "改。" -#: src/ch00/basics/errors.md:6 +#: src/getting-started/basics/errors.md:6 msgid "To throw an error, use the `assert` or `panic` functions:" msgstr "要抛出错误,请使用 `assert` 或 `panic`函数:" -#: src/ch00/basics/errors.md:8 +#: src/getting-started/basics/errors.md:8 msgid "" "- `assert` is used to validate conditions.\n" " If the check fails, an error is thrown along with a specified value, often " @@ -1358,11 +1358,11 @@ msgstr "" "`revert` 语句。\n" " (使用`panic_with_felt252` 可以直接传递一个felt252作为错误值)" -#: src/ch00/basics/errors.md:16 +#: src/getting-started/basics/errors.md:16 msgid "Here's a simple example that demonstrates the use of these functions:" msgstr "下面是一个简单的示例,演示了这些函数的用法:" -#: src/ch00/basics/errors.md:18 +#: src/getting-started/basics/errors.md:18 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1422,7 +1422,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/errors.md:46 +#: src/getting-started/basics/errors.md:46 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x0022664463FF0b711CC9B549a9E87d65A0882bB1D29338C4108696B8F2216a40) " @@ -1436,17 +1436,17 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/errors/src/simple_errors.cairo) 中尝试它。" -#: src/ch00/basics/errors.md:48 +#: src/getting-started/basics/errors.md:48 msgid "## Custom errors" msgstr "## 自定义错误" -#: src/ch00/basics/errors.md:50 +#: src/getting-started/basics/errors.md:50 msgid "" "You can make error handling easier by defining your error codes in a specific " "module." msgstr "您可以通过在特定模块中定义错误代码来简化错误处理。" -#: src/ch00/basics/errors.md:52 +#: src/getting-started/basics/errors.md:52 msgid "" "```rust\n" "mod Errors {\n" @@ -1516,7 +1516,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/errors.md:85 +#: src/getting-started/basics/errors.md:85 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x0501CD5da5B453a18515B5A20b8029bd7583DFE7a399ad9f79c284F7829e4A57) " @@ -1530,17 +1530,17 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/errors/src/custom_errors.cairo)中尝试它。" -#: src/ch00/basics/errors.md:87 +#: src/getting-started/basics/errors.md:87 msgid "## Vault example" msgstr "## Vault 示例" -#: src/ch00/basics/errors.md:89 +#: src/getting-started/basics/errors.md:89 msgid "" "Here's another example that demonstrates the use of errors in a more complex " "contract:" msgstr "下面是另一个示例,演示了在更复杂的合约中使用错误:" -#: src/ch00/basics/errors.md:91 +#: src/getting-started/basics/errors.md:91 msgid "" "```rust\n" "mod VaultErrors {\n" @@ -1636,7 +1636,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/errors.md:137 +#: src/getting-started/basics/errors.md:137 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x020C2da26F42A28Ef54ED428eF1810FE433784b055f9bF315C5d992b1579C268) " @@ -1650,11 +1650,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/errors/src/vault_errors.cairo) 中尝试它。" -#: src/ch00/basics/events.md:1 +#: src/getting-started/basics/events.md:1 msgid "# Events" msgstr "# 事件" -#: src/ch00/basics/events.md:3 +#: src/getting-started/basics/events.md:3 msgid "" "Events are a way to emit data from a contract. All events must be defined in " "the `Event` enum, which must be annotated with the `#[event]` attribute.\n" @@ -1670,7 +1670,7 @@ msgstr "" "据。可以对事件编制索引,以便在以后查询数据时轻松快速地访问。可以通过向字段成员" "添加`#[key]` 属性来索引事件数据。" -#: src/ch00/basics/events.md:6 +#: src/getting-started/basics/events.md:6 msgid "" "Here's a simple example of a contract using events that emit an event each " "time a counter is incremented by the \"increment\" function:" @@ -1678,7 +1678,7 @@ msgstr "" "下面是合约使用事件的简单示例,这些事件在每次计数器通过“increment”函数递增时发" "出一个事件:" -#: src/ch00/basics/events.md:8 +#: src/getting-started/basics/events.md:8 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1804,7 +1804,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/events.md:66 +#: src/getting-started/basics/events.md:66 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x022e3B59518EA04aBb5da671ea04ecC3a154400f226d2Df38eFE146741b9E2F6) " @@ -1818,11 +1818,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/getting-started/events/src/counter.cairo) 中尝试它。" -#: src/ch00/basics/storing-custom-types.md:1 +#: src/getting-started/basics/storing-custom-types.md:1 msgid "# Storing Custom Types" msgstr "# 存储自定义类型" -#: src/ch00/basics/storing-custom-types.md:3 +#: src/getting-started/basics/storing-custom-types.md:3 msgid "" "While native types can be stored in a contract's storage without any " "additional work, custom types require a bit more work. This is because at " @@ -1836,7 +1836,7 @@ msgstr "" "问题,我们需要为我们的自定义类型实现 `Store` trait。希望我们可以为我们的自定义类" "型派生这个trait - 除非它包含数组或字典。" -#: src/ch00/basics/storing-custom-types.md:5 +#: src/getting-started/basics/storing-custom-types.md:5 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1902,7 +1902,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/storing-custom-types.md:37 +#: src/getting-started/basics/storing-custom-types.md:37 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -1912,11 +1912,11 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/" "storing_custom_types/src/contract.cairo) 中尝试这个合约。" -#: src/ch00/basics/custom-types-in-entrypoints.md:1 +#: src/getting-started/basics/custom-types-in-entrypoints.md:1 msgid "# Custom types in entrypoints" msgstr "# 入口点中的自定义类型" -#: src/ch00/basics/custom-types-in-entrypoints.md:3 +#: src/getting-started/basics/custom-types-in-entrypoints.md:3 msgid "" "Using custom types in entrypoints requires our type to implement the `Serde` " "trait. This is because when calling an entrypoint, the input is sent as an " @@ -1931,7 +1931,7 @@ msgstr "" "数组。\n" "值得庆幸的是,我们可以为我们的自定义类型派生`Serde` trait。" -#: src/ch00/basics/custom-types-in-entrypoints.md:6 +#: src/getting-started/basics/custom-types-in-entrypoints.md:6 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -1997,7 +1997,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/custom-types-in-entrypoints.md:37 +#: src/getting-started/basics/custom-types-in-entrypoints.md:37 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -2007,26 +2007,26 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/" "custom_type_serde/src/contract.cairo) 中尝试这个合约。" -#: src/ch00/basics/documentation.md:1 +#: src/getting-started/basics/documentation.md:1 msgid "# Documentation" msgstr "# 文档" -#: src/ch00/basics/documentation.md:3 +#: src/getting-started/basics/documentation.md:3 msgid "" "It's important to take the time to document your code. It will helps " "developers and users to understand the contract and its functionalities." msgstr "" "花时间为你的代码写文档非常重要。它将帮助开发人员和用户了解合约及其功能。" -#: src/ch00/basics/documentation.md:5 +#: src/getting-started/basics/documentation.md:5 msgid "In Cairo, you can add comments with `//`." msgstr "在Cairo,您可以使用“//”添加注释。" -#: src/ch00/basics/documentation.md:7 +#: src/getting-started/basics/documentation.md:7 msgid "### Best Practices:" msgstr "### 最佳实践:" -#: src/ch00/basics/documentation.md:9 +#: src/getting-started/basics/documentation.md:9 msgid "" "Since Cairo 1, the community has adopted a [Rust-like documentation style]" "(https://doc.rust-lang.org/rust-by-example/meta/doc.html)." @@ -2034,11 +2034,11 @@ msgstr "" "自 Cairo 1 以来,社区采用了 [类似 Rust 的文档风格](https://doc.rust-lang.org/" "rust-by-example/meta/doc.html)。" -#: src/ch00/basics/documentation.md:11 +#: src/getting-started/basics/documentation.md:11 msgid "### Contract Interface:" msgstr "### 合约接口:" -#: src/ch00/basics/documentation.md:13 +#: src/getting-started/basics/documentation.md:13 msgid "" "In smart contracts, you will often have a trait that defines the contract's " "interface (with `#[starknet::interface]`).\n" @@ -2051,7 +2051,7 @@ msgstr "" "这是包含详细文档的理想场所,这些文档解释了合约入口点的用途和功能。您可以遵循以" "下模板:" -#: src/ch00/basics/documentation.md:16 +#: src/getting-started/basics/documentation.md:16 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -2087,7 +2087,7 @@ msgstr "" "}\n" "```" -#: src/ch00/basics/documentation.md:33 +#: src/getting-started/basics/documentation.md:33 msgid "" "Keep in mind that this should not describe the implementation details of the " "function, but rather the high-level purpose and functionality of the contract " @@ -2096,42 +2096,42 @@ msgstr "" "请记住,这不应该描述函数的实现细节,而应该从用户的角度描述合约的高级目的和功" "能。" -#: src/ch00/basics/documentation.md:35 +#: src/getting-started/basics/documentation.md:35 msgid "### Implementation Details:" msgstr "### 实装细节:" -#: src/ch00/basics/documentation.md:37 +#: src/getting-started/basics/documentation.md:37 msgid "" "When writing the logic of the contract, you can add comments to describe the " "technical implementation details of the functions." msgstr "在编写合约逻辑时,可以添加注释来描述函数的技术实现细节。" -#: src/ch00/basics/documentation.md:39 +#: src/getting-started/basics/documentation.md:39 msgid "" "> Avoid over-commenting: Comments should provide additional value and clarity." msgstr "> 避免过度注释:注释应提供额外的价值和清晰度。" -#: src/ch00/basics/documentation.md:41 +#: src/getting-started/basics/documentation.md:41 msgid "
Last change: 2023-12-05
" msgstr "
Last change: 2023-12-05
" -#: src/ch00/interacting/interacting.md:1 +#: src/getting-started/interacting/interacting.md:1 msgid "# Deploy and interact with contracts" msgstr "# 部署合约并与合约交互" -#: src/ch00/interacting/interacting.md:3 +#: src/getting-started/interacting/interacting.md:3 msgid "In this chapter, we will see how to deploy and interact with contracts." msgstr "在本章中,我们将了解如何部署合约并与之交互。" -#: src/ch00/interacting/interacting.md:4 +#: src/getting-started/interacting/interacting.md:4 msgid "
Last change: 2023-10-19
" msgstr "
Last change: 2023-10-19
" -#: src/ch00/interacting/interfaces-traits.md:1 +#: src/getting-started/interacting/interfaces-traits.md:1 msgid "# Contract interfaces and Traits generation" msgstr "# 合约接口和Trait生成" -#: src/ch00/interacting/interfaces-traits.md:3 +#: src/getting-started/interacting/interfaces-traits.md:3 msgid "" "Contract interfaces define the structure and behavior of a contract, serving " "as the contract's public ABI. They list all the function signatures that a " @@ -2143,7 +2143,7 @@ msgstr "" "签名。接口的详细说明可以参考 [Cairo之书](https://book.cairo-lang.org/" "ch99-01-02-a-simple-contract.html)。" -#: src/ch00/interacting/interfaces-traits.md:5 +#: src/getting-started/interacting/interfaces-traits.md:5 msgid "" "In cairo, to specify the interface you need to define a trait annotated with " "`#[starknet::interface]` and then implement that trait in the contract." @@ -2151,7 +2151,7 @@ msgstr "" "在cairo中,要指定接口,您需要定义一个带有`#[starknet::interface]`注释的trait," "然后在合约中实现该trait。" -#: src/ch00/interacting/interfaces-traits.md:7 +#: src/getting-started/interacting/interfaces-traits.md:7 msgid "" "When a function needs to access the contract state, it must have a `self` " "parameter of type `ContractState`. This implies that the corresponding " @@ -2163,7 +2163,7 @@ msgstr "" "着接口trait中的相应函数签名也必须采用`TContractState`类型作为参数。需要注意的" "是,合约接口中的每个函数都必须具有此类型为`TContractState`的`self`参数。" -#: src/ch00/interacting/interfaces-traits.md:9 +#: src/getting-started/interacting/interfaces-traits.md:9 msgid "" "You can use the `#[generate_trait]` attribute to implicitly generate the " "trait for a specific implementation block. This attribute automatically " @@ -2179,11 +2179,11 @@ msgstr "" "参数。但是,您需要使用`#[abi(per_item)]` 属性注释块,并且每个函数都具有适当的" "属性,具体取决于它是外部函数、构造函数还是 l1 处理程序。" -#: src/ch00/interacting/interfaces-traits.md:11 +#: src/getting-started/interacting/interfaces-traits.md:11 msgid "In summary, there's two ways to handle interfaces:" msgstr "总之,有两种方法可以处理接口:" -#: src/ch00/interacting/interfaces-traits.md:13 +#: src/getting-started/interacting/interfaces-traits.md:13 msgid "" "- Explicitly, by defining a trait annoted with `#[starknet::interface]`\n" "- Implicitly, by using `#[generate_trait]` combined with the " @@ -2194,11 +2194,11 @@ msgstr "" "- 隐式地,通过将`#[generate_trait]`与`#[abi(per_item)]`属性结合使用,并使用适" "当的属性注释实现块中的每个函数。" -#: src/ch00/interacting/interfaces-traits.md:16 +#: src/getting-started/interacting/interfaces-traits.md:16 msgid "## Explicit interface" msgstr "## 显式接口" -#: src/ch00/interacting/interfaces-traits.md:18 +#: src/getting-started/interacting/interfaces-traits.md:18 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -2256,7 +2256,7 @@ msgstr "" "}\n" "```" -#: src/ch00/interacting/interfaces-traits.md:45 +#: src/getting-started/interacting/interfaces-traits.md:45 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -2266,11 +2266,11 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/" "interfaces_traits/src/explicit.cairo)中尝试这个合约。" -#: src/ch00/interacting/interfaces-traits.md:47 +#: src/getting-started/interacting/interfaces-traits.md:47 msgid "## Implicit interface" msgstr "## 隐式接口" -#: src/ch00/interacting/interfaces-traits.md:49 +#: src/getting-started/interacting/interfaces-traits.md:49 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -2320,7 +2320,7 @@ msgstr "" "}\n" "```" -#: src/ch00/interacting/interfaces-traits.md:73 +#: src/getting-started/interacting/interfaces-traits.md:73 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -2330,7 +2330,7 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/" "interfaces_traits/src/implicit.cairo) 中尝试这个合约。" -#: src/ch00/interacting/interfaces-traits.md:75 +#: src/getting-started/interacting/interfaces-traits.md:75 msgid "" "> Note: You can import an implicitly generated contract interface with `use " "contract::{GeneratedContractInterface}`. However, the `Dispatcher` will not " @@ -2339,11 +2339,11 @@ msgstr "" "> 注意:您可以使用`use contract::{GeneratedContractInterface}`导入隐式生成的合" "约接口。但是,`Dispatcher`不会自动生成。" -#: src/ch00/interacting/interfaces-traits.md:77 +#: src/getting-started/interacting/interfaces-traits.md:77 msgid "## Internal functions" msgstr "## 内部函数" -#: src/ch00/interacting/interfaces-traits.md:79 +#: src/getting-started/interacting/interfaces-traits.md:79 msgid "" "You can also use `#[generate_trait]` for your internal functions.\n" "Since this trait is generated in the context of the contract, you can define " @@ -2353,7 +2353,7 @@ msgstr "" "由于此trait是在合约的上下文中生成的,因此您也可以定义纯函数(没有“self”参数的函" "数)。" -#: src/ch00/interacting/interfaces-traits.md:82 +#: src/getting-started/interacting/interfaces-traits.md:82 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -2447,7 +2447,7 @@ msgstr "" "}\n" "```" -#: src/ch00/interacting/interfaces-traits.md:127 +#: src/getting-started/interacting/interfaces-traits.md:127 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -2458,15 +2458,15 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/" "interfaces_traits/src/implicit_internal.cairo) 中尝试这个合约。" -#: src/ch00/interacting/calling_other_contracts.md:1 +#: src/getting-started/interacting/calling_other_contracts.md:1 msgid "# Calling other contracts" msgstr "# 调用其他合约" -#: src/ch00/interacting/calling_other_contracts.md:3 +#: src/getting-started/interacting/calling_other_contracts.md:3 msgid "There are two different ways to call other contracts in Cairo." msgstr "在Cairo,有两种不同的方式可以调用其他合约。" -#: src/ch00/interacting/calling_other_contracts.md:5 +#: src/getting-started/interacting/calling_other_contracts.md:5 msgid "" "The easiest way to call other contracts is by using the dispatcher of the " "contract you want to call.\n" @@ -2479,7 +2479,7 @@ msgstr "" "dispatcher-library-dispatcher-and-system-calls.html#contract-dispatcher) 中阅" "读有关 Dispatchers 的更多信息" -#: src/ch00/interacting/calling_other_contracts.md:8 +#: src/getting-started/interacting/calling_other_contracts.md:8 msgid "" "The other way is to use the `starknet::call_contract_syscall` syscall " "yourself. However, this method is not recommended." @@ -2487,7 +2487,7 @@ msgstr "" "另一种方法是自己使用`starknet::call_contract_syscall`系统调用。但是,不建议使" "用此方法。" -#: src/ch00/interacting/calling_other_contracts.md:10 +#: src/getting-started/interacting/calling_other_contracts.md:10 msgid "" "In order to call other contracts using dispatchers, you will need to define " "the called contract's interface as a trait annotated with the `#[starknet::" @@ -2498,7 +2498,7 @@ msgstr "" "interface]` 属性注释的trait,然后将 `IContractDispatcher` 和 " "`IContractDispatcherTrait` 项导入到合约中。" -#: src/ch00/interacting/calling_other_contracts.md:12 +#: src/getting-started/interacting/calling_other_contracts.md:12 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -2546,7 +2546,7 @@ msgstr "" "}\n" "```" -#: src/ch00/interacting/calling_other_contracts.md:34 +#: src/getting-started/interacting/calling_other_contracts.md:34 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x015c3Bb6D0DE26b64FEAF9A8f4655CfADb5c128bF4510398972704ee12775DB1) " @@ -2561,7 +2561,7 @@ msgstr "" "blob/main/listings/getting-started/calling_other_contracts/src/callee.cairo)中" "尝试它。" -#: src/ch00/interacting/calling_other_contracts.md:36 +#: src/getting-started/interacting/calling_other_contracts.md:36 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -2633,7 +2633,7 @@ msgstr "" "}\n" "```" -#: src/ch00/interacting/calling_other_contracts.md:68 +#: src/getting-started/interacting/calling_other_contracts.md:68 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x05fa8aF796343d2f22c53C17149386b67B7AC4aB52D9e308Aa507C185aA44778) " @@ -2648,18 +2648,18 @@ msgstr "" "blob/main/listings/getting-started/calling_other_contracts/src/caller.cairo) " "中尝试它。" -#: src/ch00/interacting/factory.md:1 +#: src/getting-started/interacting/factory.md:1 msgid "# Factory Pattern" msgstr "# 工厂模式" -#: src/ch00/interacting/factory.md:3 +#: src/getting-started/interacting/factory.md:3 msgid "" "The factory pattern is a well known pattern in object oriented programming. " "It provides an abstraction on how to instantiate a class. " msgstr "" "工厂模式是面向对象编程中众所周知的模式。它提供了有关如何实例化类的抽象。" -#: src/ch00/interacting/factory.md:5 +#: src/getting-started/interacting/factory.md:5 msgid "" "In the case of smart contracts, we can use this pattern by defining a factory " "contract that have the sole responsibility of creating and managing other " @@ -2668,11 +2668,11 @@ msgstr "" "在智能合约里,我们可以通过定义一个工厂合约来使用这种模式,该合约全权负责创建和" "管理其他合约。" -#: src/ch00/interacting/factory.md:7 +#: src/getting-started/interacting/factory.md:7 msgid "## Class hash and contract instance" msgstr "## 类哈希(Class hash)和合约实例" -#: src/ch00/interacting/factory.md:9 +#: src/getting-started/interacting/factory.md:9 msgid "" "In Starknet, there's a separation between contract's classes and instances. A " "contract class serves as a blueprint, defined by the underling Cairo " @@ -2684,7 +2684,7 @@ msgstr "" "约的入口点、ABI 和 Sierra 程序哈希定义。合约类由类哈希标识。当您想向网络添加一" "个新类时,首先需要声明它。" -#: src/ch00/interacting/factory.md:11 +#: src/getting-started/interacting/factory.md:11 msgid "" "When deploying a contract, you need to specify the class hash of the contract " "you want to deploy. Each instance of a contract has their own storage " @@ -2693,23 +2693,23 @@ msgstr "" "部署合约时,需要指定要部署的合约的类哈希值。合约的每个实例都有自己的存储,这与" "类哈希无关。" -#: src/ch00/interacting/factory.md:13 +#: src/getting-started/interacting/factory.md:13 msgid "" "Using the factory pattern, we can deploy multiple instances of the same " "contract class and handle upgrades easily." msgstr "使用工厂模式,我们可以部署同一合约类的多个实例,并轻松处理升级。" -#: src/ch00/interacting/factory.md:15 +#: src/getting-started/interacting/factory.md:15 msgid "## Minimal example" msgstr "## 最小范例" -#: src/ch00/interacting/factory.md:17 +#: src/getting-started/interacting/factory.md:17 msgid "" "Here's a minimal example of a factory contract that deploy the " "`SimpleCounter` contract:" msgstr "下面是部署`SimpleCounter` 合约的工厂合约的最小范例:" -#: src/ch00/interacting/factory.md:19 +#: src/getting-started/interacting/factory.md:19 msgid "" "```rust\n" "use starknet::{ContractAddress, ClassHash};\n" @@ -2861,7 +2861,7 @@ msgstr "" "}\n" "```" -#: src/ch00/interacting/factory.md:86 +#: src/getting-started/interacting/factory.md:86 msgid "" "\n" @@ -2905,11 +2905,11 @@ msgstr "" "\n" "
Last change: 2023-12-07
" -#: src/ch00/testing/contract-testing.md:1 +#: src/getting-started/testing/contract-testing.md:1 msgid "# Contract Testing" msgstr "# 合约测试" -#: src/ch00/testing/contract-testing.md:3 +#: src/getting-started/testing/contract-testing.md:3 msgid "" "Testing plays a crucial role in software development, especially for smart " "contracts. In this section, we'll guide you through the basics of testing a " @@ -2918,11 +2918,11 @@ msgstr "" "测试在软件开发中起着至关重要的作用,尤其是对于智能合约而言。在本节中,我们将通" "过Starknet上的`scarb` ,引导你了解智能合约测试的基础知识。" -#: src/ch00/testing/contract-testing.md:5 +#: src/getting-started/testing/contract-testing.md:5 msgid "Let's start with a simple smart contract as an example:" msgstr "让我们以一个简单的智能合约作为例子开始:" -#: src/ch00/testing/contract-testing.md:6 +#: src/getting-started/testing/contract-testing.md:6 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -3012,11 +3012,11 @@ msgstr "" "}\n" "```" -#: src/ch00/testing/contract-testing.md:50 +#: src/getting-started/testing/contract-testing.md:50 msgid "Now, take a look at the tests for this contract:" msgstr "现在,让我们看一下这个合约的测试:" -#: src/ch00/testing/contract-testing.md:51 +#: src/getting-started/testing/contract-testing.md:51 msgid "" "```rust\n" "#[cfg(test)]\n" @@ -3194,7 +3194,7 @@ msgstr "" "}\n" "```" -#: src/ch00/testing/contract-testing.md:132 +#: src/getting-started/testing/contract-testing.md:132 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -3204,7 +3204,7 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/getting-started/" "testing_how_to/src/lib.cairo)中与这个合约进行互动。" -#: src/ch00/testing/contract-testing.md:134 +#: src/getting-started/testing/contract-testing.md:134 msgid "" "To define our test, we use scarb, which allows us to create a separate module " "guarded with `#[cfg(test)]`. This ensures that the test module is only " @@ -3213,7 +3213,7 @@ msgstr "" "为了定义我们的测试,我们使用 scarb,它允许我们创建一个被 `#[cfg(test)]` 保护的" "独立模块。这样可以确保测试模块只在使用 `scarb test` 运行测试时被编译。" -#: src/ch00/testing/contract-testing.md:136 +#: src/getting-started/testing/contract-testing.md:136 msgid "" "Each test is defined as a function with the `#[test]` attribute. You can also " "check if a test panics using the `#[should_panic]` attribute." @@ -3221,7 +3221,7 @@ msgstr "" "每个测试都被定义为带有 `#[test]` 属性的函数。您还可以使用 `#[should_panic]` 属" "性检查测试是否会引发 panic。" -#: src/ch00/testing/contract-testing.md:138 +#: src/getting-started/testing/contract-testing.md:138 msgid "" "As we are in the context of a smart contract, it's essential to set up the " "gas limit. You do this by using the `#[available_gas(X)]` attribute to " @@ -3232,15 +3232,15 @@ msgstr "" "`#[available_gas(X)]` 属性来指定测试的 gas 限制。这也是确保合约功能保持在某个" "特定 gas 限制下的好方法!" -#: src/ch00/testing/contract-testing.md:140 +#: src/getting-started/testing/contract-testing.md:140 msgid "> Note: The term \"gas\" here refers to Sierra gas, not L1 gas" msgstr "> 注意:这里的 “gas” 一词指的是 Sierra gas,而不是 L1 的 gas" -#: src/ch00/testing/contract-testing.md:142 +#: src/getting-started/testing/contract-testing.md:142 msgid "Now, let's move on to the testing process:" msgstr "现在,让我们进入测试过程:" -#: src/ch00/testing/contract-testing.md:143 +#: src/getting-started/testing/contract-testing.md:143 msgid "" "- Use the `deploy` function logic to declare and deploy your contract.\n" "- Use `assert` to verify that the contract behaves as expected in the given " @@ -3249,13 +3249,13 @@ msgstr "" "- 使用 `deploy` 函数的逻辑来声明和部署您的合约。\n" "- 使用 `assert` 来验证合约在给定的上下文中的行为是否符合预期。" -#: src/ch00/testing/contract-testing.md:146 +#: src/getting-started/testing/contract-testing.md:146 msgid "" "To make testing more convenient, the `testing` module of the corelib provides " "some helpful functions:" msgstr "为了使测试更加方便,corelib 的 `testing` 模块提供了一些有用的函数:" -#: src/ch00/testing/contract-testing.md:147 +#: src/getting-started/testing/contract-testing.md:147 msgid "" "- `set_caller_address(address: ContractAddress)`\n" "- `set_contract_address(address: ContractAddress)`\n" @@ -3271,14 +3271,14 @@ msgstr "" "- `set_account_contract_address(address: ContractAddress)`\n" "- `set_max_fee(fee: u128)`" -#: src/ch00/testing/contract-testing.md:154 +#: src/getting-started/testing/contract-testing.md:154 msgid "" "You may also need the `info` module from the corelib, which allows you to " "access information about the current transaction context:" msgstr "" "你可能还需要 corelib 中的 `info` 模块,它允许你访问有关当前交易上下文的信息:" -#: src/ch00/testing/contract-testing.md:155 +#: src/getting-started/testing/contract-testing.md:155 msgid "" "- `get_caller_address() -> ContractAddress`\n" "- `get_contract_address() -> ContractAddress`\n" @@ -3294,7 +3294,7 @@ msgstr "" "- `get_block_timestamp() -> u64`\n" "- `get_block_number() -> u64`" -#: src/ch00/testing/contract-testing.md:163 +#: src/getting-started/testing/contract-testing.md:163 msgid "" "You can found the full list of functions in the [Starknet Corelib repo]" "(https://github.com/starkware-libs/cairo/tree/main/corelib/src/starknet).\n" @@ -3307,15 +3307,15 @@ msgstr "" "你还可以在 [Cairo book 第8章](https://book.cairo-lang.org/ch08-01-how-to-" "write-tests.html) 中找到有关在 cairo 中进行测试的详细说明。" -#: src/ch00/testing/contract-testing.md:166 +#: src/getting-started/testing/contract-testing.md:166 msgid "## Starknet Foundry" msgstr "## Starknet Foundry" -#: src/ch00/testing/contract-testing.md:168 +#: src/getting-started/testing/contract-testing.md:168 msgid "" msgstr "" -#: src/ch00/testing/contract-testing.md:170 +#: src/getting-started/testing/contract-testing.md:170 msgid "" "Starknet Foundry is a powerful toolkit for developing smart contracts on " "Starknet. It offers support for testing Starknet smart contracts on top of " @@ -3324,7 +3324,7 @@ msgstr "" "Starknet Foundry是在Starknet上开发智能合约的强大工具包。它提供了对使用`Forge` " "工具在 `scarb` 上测试Starknet智能合约的支持。" -#: src/ch00/testing/contract-testing.md:172 +#: src/getting-started/testing/contract-testing.md:172 msgid "" "Testing with `snforge` is similar to the process we just described but " "simplified. Moreover, additional features are on the way, including " @@ -3335,7 +3335,7 @@ msgstr "" "正在开发中,包括作弊码或并行测试执行。我们强烈推荐探索Starknet Foundry并将其纳" "入您你的项目中。" -#: src/ch00/testing/contract-testing.md:174 +#: src/getting-started/testing/contract-testing.md:174 msgid "" "For more detailed information about testing contracts with Starknet Foundry, " "check out the [Starknet Foundry Book - Testing Contracts](https://foundry-rs." @@ -3345,25 +3345,25 @@ msgstr "" "合约测试](https://foundry-rs.github.io/starknet-foundry/testing/contracts." "html)。" -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:1 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:1 msgid "# Cairo Cheatsheet" msgstr "# Cairo 备忘单" -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:3 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:3 msgid "" "This chapter aims to provide a quick reference for the most common Cairo " "constructs." msgstr "本章旨在为最常见的Cairo结构提供快速参考。" -#: src/ch00/cairo_cheatsheet/cairo_cheatsheet.md:5 +#: src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md:5 msgid "
Last change: 2023-10-31
" msgstr "
Last change: 2023-10-31
" -#: src/ch00/cairo_cheatsheet/felt.md:1 +#: src/getting-started/cairo_cheatsheet/felt.md:1 msgid "# Felt252" msgstr "# Felt252" -#: src/ch00/cairo_cheatsheet/felt.md:3 +#: src/getting-started/cairo_cheatsheet/felt.md:3 msgid "" "Felt252 is a fundamental data type in Cairo from which all other data types " "are derived.\n" @@ -3373,12 +3373,12 @@ msgstr "" "Felt252是Cairo中的基本数据类型,所有其他数据类型都派生自它。\n" "Felt252也可以用于存储最多31个字符长度的短字符串表示。" -#: src/ch00/cairo_cheatsheet/felt.md:6 src/ch00/cairo_cheatsheet/arrays.md:20 -#: src/ch02/hash-solidity-compatible.md:5 +#: src/getting-started/cairo_cheatsheet/felt.md:6 src/getting-started/cairo_cheatsheet/arrays.md:20 +#: src/advanced-concepts/hash-solidity-compatible.md:5 msgid "For example:" msgstr "例如:" -#: src/ch00/cairo_cheatsheet/felt.md:8 +#: src/getting-started/cairo_cheatsheet/felt.md:8 msgid "" "```rust\n" " let felt: felt252 = 100;\n" @@ -3394,16 +3394,16 @@ msgstr "" " let felt = felt + felt_as_str;\n" "```" -#: src/ch00/cairo_cheatsheet/mapping.md:1 +#: src/getting-started/cairo_cheatsheet/mapping.md:1 msgid "# Mapping" msgstr "# Mapping" -#: src/ch00/cairo_cheatsheet/mapping.md:3 +#: src/getting-started/cairo_cheatsheet/mapping.md:3 msgid "" "The ```LegacyMap``` type can be used to represent a collection of key-value." msgstr "`LegacyMap` 类型可以用于表示键值对的集合。" -#: src/ch00/cairo_cheatsheet/mapping.md:5 +#: src/getting-started/cairo_cheatsheet/mapping.md:5 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -3532,11 +3532,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/arrays.md:1 +#: src/getting-started/cairo_cheatsheet/arrays.md:1 msgid "# Arrays" msgstr "# 数组" -#: src/ch00/cairo_cheatsheet/arrays.md:3 +#: src/getting-started/cairo_cheatsheet/arrays.md:3 msgid "" "Arrays are collections of elements of the same type.\n" "The possible operations on arrays are defined with the `array::ArrayTrait` of " @@ -3545,7 +3545,7 @@ msgstr "" "数组是相同类型元素的集合。\n" "可以使用 corelib 的 `array::ArrayTrait` 来定义可能的数组操作:" -#: src/ch00/cairo_cheatsheet/arrays.md:6 +#: src/getting-started/cairo_cheatsheet/arrays.md:6 msgid "" "```rust\n" "trait ArrayTrait {\n" @@ -3575,7 +3575,7 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/arrays.md:22 +#: src/getting-started/cairo_cheatsheet/arrays.md:22 msgid "" "```rust\n" "fn array() -> bool {\n" @@ -3617,11 +3617,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/loop.md:1 +#: src/getting-started/cairo_cheatsheet/loop.md:1 msgid "# Loop" msgstr "# 循环" -#: src/ch00/cairo_cheatsheet/loop.md:3 +#: src/getting-started/cairo_cheatsheet/loop.md:3 msgid "" "A loop specifies a block of code that will run repetitively until a halting " "condition is encountered.\n" @@ -3630,7 +3630,7 @@ msgstr "" "循环指定一个代码块,该代码块将重复运行,直到遇到停止条件。\n" "例如:" -#: src/ch00/cairo_cheatsheet/loop.md:6 +#: src/getting-started/cairo_cheatsheet/loop.md:6 msgid "" "```rust\n" " let mut arr = ArrayTrait::new();\n" @@ -3666,11 +3666,11 @@ msgstr "" " };\n" "```" -#: src/ch00/cairo_cheatsheet/match.md:1 +#: src/getting-started/cairo_cheatsheet/match.md:1 msgid "# Match" msgstr "# 分支" -#: src/ch00/cairo_cheatsheet/match.md:3 +#: src/getting-started/cairo_cheatsheet/match.md:3 msgid "" "The `match` expression in Cairo allows us to control the flow of our code " "by comparing a `felt252` data type or an enum against various patterns and then " @@ -3680,7 +3680,7 @@ msgstr "" "在 Cairo 中,`match` 表达式允许我们通过将 `felt252` 数据类型或枚举与各种模式进行比" "较,然后根据匹配的模式运行特定的代码来控制代码的流程。例如:" -#: src/ch00/cairo_cheatsheet/match.md:6 +#: src/getting-started/cairo_cheatsheet/match.md:6 msgid "" "```rust\n" "#[derive(Drop, Serde)]\n" @@ -3788,11 +3788,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/tuples.md:1 +#: src/getting-started/cairo_cheatsheet/tuples.md:1 msgid "# Tuples" msgstr "# 元组" -#: src/ch00/cairo_cheatsheet/tuples.md:3 +#: src/getting-started/cairo_cheatsheet/tuples.md:3 msgid "" "Tuples is a data type to group a fixed number of items of potentially " "different types into a single compound structure. Unlike arrays, tuples have " @@ -3805,7 +3805,7 @@ msgstr "" "就无法更改。\n" "例如:" -#: src/ch00/cairo_cheatsheet/tuples.md:6 +#: src/getting-started/cairo_cheatsheet/tuples.md:6 msgid "" "```rust\n" " let address = \"0x000\";\n" @@ -3831,11 +3831,11 @@ msgstr "" " let (address, age, active) = stored_tuple;\n" "```" -#: src/ch00/cairo_cheatsheet/struct.md:1 +#: src/getting-started/cairo_cheatsheet/struct.md:1 msgid "# Struct" msgstr "# 结构体" -#: src/ch00/cairo_cheatsheet/struct.md:3 +#: src/getting-started/cairo_cheatsheet/struct.md:3 msgid "" "A struct is a data type similar to tuple. Like tuples they can be used to " "hold data of different types.\n" @@ -3845,7 +3845,7 @@ msgstr "" "据。\n" "例如:" -#: src/ch00/cairo_cheatsheet/struct.md:6 +#: src/getting-started/cairo_cheatsheet/struct.md:6 msgid "" "```rust\n" "// With Store, you can store Data's structs in the storage part of " @@ -3866,11 +3866,11 @@ msgstr "" "}\n" "```" -#: src/ch00/cairo_cheatsheet/type_casting.md:1 +#: src/getting-started/cairo_cheatsheet/type_casting.md:1 msgid "# Type casting" msgstr "# 类型转换" -#: src/ch00/cairo_cheatsheet/type_casting.md:3 +#: src/getting-started/cairo_cheatsheet/type_casting.md:3 msgid "" "Cairo supports the conversion from one scalar types to another by using the " "into and try_into methods.\n" @@ -3884,7 +3884,7 @@ msgstr "" "用于从较大的数据类型转换为较小的数据类型,可能会发生溢出的情况。\n" "例如:" -#: src/ch00/cairo_cheatsheet/type_casting.md:7 +#: src/getting-started/cairo_cheatsheet/type_casting.md:7 msgid "" "```rust\n" " let a_number: u32 = 15;\n" @@ -4154,11 +4154,11 @@ msgid "" "Components](https://book.cairo-lang.org/ch99-01-05-00-components.html)." msgstr "" -#: src/ch01/upgradeable_contract.md:1 +#: src/applications/upgradeable_contract.md:1 msgid "# Upgradeable Contract" msgstr "# 可升级合约" -#: src/ch01/upgradeable_contract.md:3 +#: src/applications/upgradeable_contract.md:3 msgid "" "In Starknet, contracts are divided into two parts: contract classes and " "contract\n" @@ -4171,7 +4171,7 @@ msgstr "" "这种划分遵循了面向对象编程语言中的类和实例的概念。\n" "这样,我们区分了对象的定义和实现。" -#: src/ch01/upgradeable_contract.md:8 +#: src/applications/upgradeable_contract.md:8 msgid "" "A contract class is the definition of a contract: it specifies how the " "contract\n" @@ -4183,7 +4183,7 @@ msgstr "" "合约类包含了关键信息,如Cairo字节码、提示信息、入口点名称等,\n" "以及一切明确定义合约类语义的内容。" -#: src/ch01/upgradeable_contract.md:13 +#: src/applications/upgradeable_contract.md:13 msgid "" "To identify different contract classes, Starknet assigns a unique identifier " "to each\n" @@ -4197,7 +4197,7 @@ msgstr "" "合约实例是对应于特定合约类的已部署合约。\n" "可以将其视为在诸如Java等语言中对象的一个实例。" -#: src/ch01/upgradeable_contract.md:18 +#: src/applications/upgradeable_contract.md:18 msgid "" "Each class is identified by its class hash, which is analogous to a class " "name in an object-oriented programming language. A contract instance is a " @@ -4206,7 +4206,7 @@ msgstr "" "每个类由其类哈希值标识,类似于面向对象编程语言中的类名。合约实例是对应于某个类" "的已部署合约。" -#: src/ch01/upgradeable_contract.md:20 +#: src/applications/upgradeable_contract.md:20 msgid "" "You can upgrade a deployed contract to a newer version by calling the " "`replace_class_syscall` function. By using this function, you can update the " @@ -4218,7 +4218,7 @@ msgstr "" "使用这个函数,你可以更新与已部署合约相关联的类哈希,从而有效地升级合约的实现。" "然而,这不会修改合约中的存储,因此合约中存储的所有数据将保持不变。" -#: src/ch01/upgradeable_contract.md:22 +#: src/applications/upgradeable_contract.md:22 msgid "" "To illustrate this concept, let's consider an example with two contracts: " "`UpgradeableContract_V0`, and `UpgradeableContract_V1`.\n" @@ -4235,7 +4235,7 @@ msgstr "" "函数的交易,将部署合约的类哈希升级为`UpgradeableContract_V1`的类哈希。然后,调" "用合约上的`version`方法,查看合约是否已升级到V1版本。" -#: src/ch01/upgradeable_contract.md:25 +#: src/applications/upgradeable_contract.md:25 msgid "" "```rust\n" "use starknet::class_hash::ClassHash;\n" @@ -4327,7 +4327,7 @@ msgstr "" "}\n" "```" -#: src/ch01/upgradeable_contract.md:68 +#: src/applications/upgradeable_contract.md:68 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x005300003ade5d10447d941a42d48b7141074cd8bade2b16520684896a5090ea) " @@ -4343,7 +4343,7 @@ msgstr "" "blob/main/listings/applications/upgradeable_contract/src/" "upgradeable_contract_v0.cairo)中尝试它 。" -#: src/ch01/upgradeable_contract.md:71 +#: src/applications/upgradeable_contract.md:71 msgid "" "```rust\n" "use starknet::class_hash::ClassHash;\n" @@ -4435,7 +4435,7 @@ msgstr "" "}\n" "```" -#: src/ch01/upgradeable_contract.md:114 +#: src/applications/upgradeable_contract.md:114 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x017c86152badd1d665b9836571bd6b0a484f028748aa13d9b2d5d9c9192fafc6) " @@ -4451,11 +4451,11 @@ msgstr "" "blob/main/listings/applications/upgradeable_contract/src/" "upgradeable_contract_v1.cairo) 中尝试它。" -#: src/ch01/simple_vault.md:1 +#: src/applications/simple_vault.md:1 msgid "# Simple Defi Vault" msgstr "# 简单的去中心化金融保险库" -#: src/ch01/simple_vault.md:3 +#: src/applications/simple_vault.md:3 msgid "" "This is the Cairo adaptation of the [Solidity by example Vault](https://" "solidity-by-example.org/defi/vault/).\n" @@ -4465,7 +4465,7 @@ msgstr "" "的Cairo版本\n" "以下是它的工作原理:" -#: src/ch01/simple_vault.md:6 +#: src/applications/simple_vault.md:6 msgid "" "- When a user deposits a token, the contract calculates the amount of shares " "to mint.\n" @@ -4478,7 +4478,7 @@ msgstr "" "- 当用户取款时,合约会销毁他们的份额,计算收益,并提取存款的收益和初始代币金" "额。" -#: src/ch01/simple_vault.md:10 +#: src/applications/simple_vault.md:10 msgid "" "```rust\n" "use starknet::{ContractAddress};\n" @@ -4704,7 +4704,7 @@ msgstr "" "\n" "```" -#: src/ch01/simple_vault.md:113 +#: src/applications/simple_vault.md:113 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -4714,11 +4714,11 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/applications/" "simple_vault/src/simple_vault.cairo) 中测试这个合约。" -#: src/ch01/erc20.md:1 +#: src/applications/erc20.md:1 msgid "# ERC20 Token" msgstr "# ERC20 代币" -#: src/ch01/erc20.md:3 +#: src/applications/erc20.md:3 msgid "" "Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/" "eip-20) are called ERC20 tokens. They are used to represent fungible assets." @@ -4726,11 +4726,11 @@ msgstr "" "遵循 [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20) 的合约被称为 " "ERC20 代币。它们用于代表可互换的资产。" -#: src/ch01/erc20.md:5 +#: src/applications/erc20.md:5 msgid "To create an ERC20 conctract, it must implement the following interface:" msgstr "要创建 ERC20 合约,必须实现以下接口:" -#: src/ch01/erc20.md:7 +#: src/applications/erc20.md:7 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -4796,7 +4796,7 @@ msgstr "" "}\n" "```" -#: src/ch01/erc20.md:33 +#: src/applications/erc20.md:33 msgid "" "In Starknet, function names should be written in *snake_case*. This is not " "the case in Solidity, where function names are written in *camelCase*.\n" @@ -4807,11 +4807,11 @@ msgstr "" "使用*camelCase*(驼峰命名法)。因此,Starknet的ERC20接口与Solidity的ERC20接口" "略有不同。" -#: src/ch01/erc20.md:36 +#: src/applications/erc20.md:36 msgid "Here's an implementation of the ERC20 interface in Cairo:" msgstr "以下是一个在Cairo中实现的ERC20接口的示例:" -#: src/ch01/erc20.md:38 +#: src/applications/erc20.md:38 msgid "" "```rust\n" "#[starknet::contract]\n" @@ -5215,7 +5215,7 @@ msgstr "" "}\n" "```" -#: src/ch01/erc20.md:224 +#: src/applications/erc20.md:224 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -5225,7 +5225,7 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/applications/erc20/src/" "token.cairo) 中测试这个合约。" -#: src/ch01/erc20.md:226 +#: src/applications/erc20.md:226 msgid "" "There's several other implementations, such as the [Open Zeppelin](https://" "docs.openzeppelin.com/contracts-cairo/0.7.0/erc20) or the [Cairo By Example]" @@ -5235,11 +5235,11 @@ msgstr "" "contracts-cairo/0.7.0/erc20) 或者 [Cairo By Example](https://cairo-by-example." "com/examples/erc20/) 中的实现。" -#: src/ch01/constant-product-amm.md:1 +#: src/applications/constant-product-amm.md:1 msgid "# Constant Product AMM" msgstr "# 恒定乘积自动做市商" -#: src/ch01/constant-product-amm.md:3 +#: src/applications/constant-product-amm.md:3 msgid "" "This is the Cairo adaptation of the [Solidity by example Constant Product AMM]" "(https://solidity-by-example.org/defi/constant-product-amm/)." @@ -5247,7 +5247,7 @@ msgstr "" "这个是 用Cairo 改编的 [Solidity by example Constant Product AMM](https://" "solidity-by-example.org/defi/constant-product-amm/)." -#: src/ch01/constant-product-amm.md:5 +#: src/applications/constant-product-amm.md:5 msgid "" "```rust\n" "use starknet::ContractAddress;\n" @@ -5847,7 +5847,7 @@ msgstr "" "}\n" "```" -#: src/ch01/constant-product-amm.md:274 +#: src/applications/constant-product-amm.md:274 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -5858,11 +5858,11 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/applications/" "constant_product_amm/src/constant_product_amm.cairo) 中测试这个合约。" -#: src/ch02/write_to_any_slot.md:1 +#: src/advanced-concepts/write_to_any_slot.md:1 msgid "# Writing to any storage slot" msgstr "# 写入任何存储槽" -#: src/ch02/write_to_any_slot.md:3 +#: src/advanced-concepts/write_to_any_slot.md:3 msgid "" "On Starknet, a contract's storage is a map with 2^251 slots, where each slot " "is a felt which is initialized to 0.\n" @@ -5876,7 +5876,7 @@ msgstr "" "pedersen(keccak(变量名), keys)`。与存储变量的交互通常使用 `self.var.read()` " "和 `self.var.write()` 。" -#: src/ch02/write_to_any_slot.md:6 +#: src/advanced-concepts/write_to_any_slot.md:6 msgid "" "Nevertheless, we can use the `storage_write_syscall` and " "`storage_read_syscall` syscalls, to write to and read from any storage slot.\n" @@ -5890,7 +5890,7 @@ msgstr "" "这在写入那些在编译时还未确定的存储变量时非常有用,这也可以确保即使合约升级且存" "储变量地址的计算方法改变,这些变量仍然可访问。" -#: src/ch02/write_to_any_slot.md:9 +#: src/advanced-concepts/write_to_any_slot.md:9 msgid "" "In the following example, we use the Poseidon hash function to compute the " "address of a storage variable. Poseidon is a ZK-friendly hash function that " @@ -5902,7 +5902,7 @@ msgstr "" "ZK 友好的哈希函数,比 Pedersen 更便宜、更快,是链上计算的绝佳选择。一旦地址被" "计算出来,我们就使用存储相关的系统调用与之交互。" -#: src/ch02/write_to_any_slot.md:11 +#: src/advanced-concepts/write_to_any_slot.md:11 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -6004,7 +6004,7 @@ msgstr "" "}\n" "```" -#: src/ch02/write_to_any_slot.md:56 +#: src/advanced-concepts/write_to_any_slot.md:56 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x033943CB781A4E63C9dcE0A1A09eAa3b617AA43CC61637C08c043a67f3fe0087) " @@ -6019,11 +6019,11 @@ msgstr "" "blob/main/listings/advanced-concepts/write_to_any_slot/src/contract.cairo) 中" "测试它." -#: src/ch02/storing_arrays.md:1 +#: src/advanced-concepts/storing_arrays.md:1 msgid "# Storing Arrays" msgstr "# 存储数组" -#: src/ch02/storing_arrays.md:3 +#: src/advanced-concepts/storing_arrays.md:3 msgid "" "On Starknet, complex values (e.g., tuples or structs), are stored in a " "continuous segment starting from the address of the storage variable. There " @@ -6040,7 +6040,7 @@ msgstr "" "前 Cairo 没有原生支持存储数组,所以你需要为你希望存储的数组类型实现自己的 " "`Store` 特性。" -#: src/ch02/storing_arrays.md:5 +#: src/advanced-concepts/storing_arrays.md:5 msgid "" "> Note: While storing arrays in storage is possible, it is not always " "recommended, as the read and write operations can get very costly. For " @@ -6054,7 +6054,7 @@ msgstr "" "为 `n` 的数组写入需要进行 `n` 次存储写入。如果你只需要一次访问数组中的一个元" "素,建议使用 `LegacyMap` 并在另一个变量中存储数组长度。" -#: src/ch02/storing_arrays.md:7 +#: src/advanced-concepts/storing_arrays.md:7 msgid "" "The following example demonstrates how to write a simple implementation of " "the `StorageAccess` trait for the `Array` type, allowing us to store " @@ -6063,7 +6063,7 @@ msgstr "" "以下示例展示了如何为 `Array` 类型实现一个简单的 `StorageAccess` 特" "性,使我们能够存储多达 255 个 `felt252` 元素的数组。" -#: src/ch02/storing_arrays.md:9 +#: src/advanced-concepts/storing_arrays.md:9 msgid "" "```rust\n" "impl StoreFelt252Array of Store> {\n" @@ -6209,13 +6209,13 @@ msgstr "" "}\n" "```" -#: src/ch02/storing_arrays.md:73 +#: src/advanced-concepts/storing_arrays.md:73 msgid "" "You can then import this implementation in your contract and use it to store " "arrays in storage:" msgstr "您可以在合约中导入上面的实现方式,并使用它来在存储中存储数组:" -#: src/ch02/storing_arrays.md:75 +#: src/advanced-concepts/storing_arrays.md:75 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -6275,7 +6275,7 @@ msgstr "" "}\n" "```" -#: src/ch02/storing_arrays.md:103 +#: src/advanced-concepts/storing_arrays.md:103 msgid "" "Visit contract on [Voyager](https://goerli.voyager.online/" "contract/0x008F8069a3Fcd7691Db46Dc3b6F9D2C0436f9200E861330957Fd780A3595da86) " @@ -6289,11 +6289,11 @@ msgstr "" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" "blob/main/listings/advanced-concepts/storing_arrays/src/contract.cairo)上测试." -#: src/ch02/struct-mapping-key.md:1 +#: src/advanced-concepts/struct-mapping-key.md:1 msgid "# Structs as mapping keys" msgstr "# 结构体作为映射键" -#: src/ch02/struct-mapping-key.md:3 +#: src/advanced-concepts/struct-mapping-key.md:3 msgid "" "In order to use structs as mapping keys, you can use `#[derive(Hash)]` on the " "struct definition. This will automatically generate a hash function for the " @@ -6302,7 +6302,7 @@ msgstr "" "为了使用结构体作为映射键,您可以在结构体定义上使用 `#[derive(Hash)]`。这将为结" "构体自动生成一个哈希函数,可以在 `LegacyMap` 中将该结构体作为键来使用。" -#: src/ch02/struct-mapping-key.md:5 +#: src/advanced-concepts/struct-mapping-key.md:5 msgid "" "Consider the following example in which we would like to use an object of\n" "type `Pet` as a key in a `LegacyMap`. The `Pet` struct has three fields: " @@ -6313,7 +6313,7 @@ msgstr "" "构体有三个字段:`name` 、`age` 和 `owner`。假设这三个字段的组合能唯一地标识一" "只宠物。" -#: src/ch02/struct-mapping-key.md:8 +#: src/advanced-concepts/struct-mapping-key.md:8 msgid "" "```rust\n" "#[derive(Copy, Drop, Serde, Hash)]\n" @@ -6389,7 +6389,7 @@ msgstr "" "}\n" "```" -#: src/ch02/struct-mapping-key.md:45 +#: src/advanced-concepts/struct-mapping-key.md:45 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -6400,11 +6400,11 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts/" "struct_as_mapping_key/src/contract.cairo) 上测试这个合约." -#: src/ch02/hash-solidity-compatible.md:1 +#: src/advanced-concepts/hash-solidity-compatible.md:1 msgid "# Hash Solidity Compatible" msgstr "# 兼容Hash Solidity" -#: src/ch02/hash-solidity-compatible.md:3 +#: src/advanced-concepts/hash-solidity-compatible.md:3 msgid "" "This contract demonstrates Keccak hashing in Cairo to match Solidity's " "keccak256. While both use Keccak, their endianness differs: Cairo is little-" @@ -6417,7 +6417,7 @@ msgstr "" "合约通过使用 `keccak_u256s_be_inputs` 以大端序进行哈希处理,并使用 " "`u128_byte_reverse` 反转结果的字节来实现兼容。" -#: src/ch02/hash-solidity-compatible.md:7 +#: src/advanced-concepts/hash-solidity-compatible.md:7 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -6497,7 +6497,7 @@ msgstr "" "}\n" "```" -#: src/ch02/hash-solidity-compatible.md:44 +#: src/advanced-concepts/hash-solidity-compatible.md:44 msgid "" "Play with the contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet&url=https://github.com/NethermindEth/StarknetByExample/" @@ -6508,19 +6508,19 @@ msgstr "" "com/NethermindEth/StarknetByExample/blob/main/listings/advanced-concepts/" "hash_solidity_compatible/src/contract.cairo) 上测试这个合约." -#: src/ch02/optimisations/optimisations.md:1 +#: src/advanced-concepts/optimisations/optimisations.md:1 msgid "# Optimisations " msgstr "# 优化" -#: src/ch02/optimisations/optimisations.md:3 +#: src/advanced-concepts/optimisations/optimisations.md:3 msgid "A collection of optimisation patterns to save gas and steps." msgstr "这里列出了一系列优化模式,用以节省Gas和计算步骤。" -#: src/ch02/optimisations/store_using_packing.md:1 +#: src/advanced-concepts/optimisations/store_using_packing.md:1 msgid "# Storage optimisation " msgstr "# 存储优化" -#: src/ch02/optimisations/store_using_packing.md:3 +#: src/advanced-concepts/optimisations/store_using_packing.md:3 msgid "" "A smart contract has a limited amount of **storage slots**. Each slot can " "store a single `felt252` value.\n" @@ -6530,7 +6530,7 @@ msgstr "" "智能合约只有有限的**存储槽位**。每个槽位可以存储一个 `felt252` 值。\n" "写入一个存储槽位会产生成本,因此我们希望尽可能少地使用存储槽位。" -#: src/ch02/optimisations/store_using_packing.md:6 +#: src/advanced-concepts/optimisations/store_using_packing.md:6 msgid "" "In Cairo, every type is derived from the `felt252` type, which uses 252 bits " "to store a value.\n" @@ -6542,11 +6542,11 @@ msgstr "" "这种设计相当简单,但它有一个缺点:它在存储效率方面并不高。例如,如果要存储一" "个 `u8` 值,我们需要使用整个槽位,尽管我们只需要 8 位。" -#: src/ch02/optimisations/store_using_packing.md:9 +#: src/advanced-concepts/optimisations/store_using_packing.md:9 msgid "## Packing" msgstr "## 打包" -#: src/ch02/optimisations/store_using_packing.md:11 +#: src/advanced-concepts/optimisations/store_using_packing.md:11 msgid "" "When storing multiple values, we can use a technique called **packing**. " "Packing is a technique that allows us to store multiple values in a single " @@ -6557,7 +6557,7 @@ msgstr "" "我们在单个 felt 值中存储多个值的技术。这是通过使用 felt 值的位来存储多个值来实" "现的。" -#: src/ch02/optimisations/store_using_packing.md:13 +#: src/advanced-concepts/optimisations/store_using_packing.md:13 msgid "" "For example, if we want to store two `u8` values, we can use the first 8 bits " "of the felt value to store the first `u8` value, and the last 8 bits to store " @@ -6568,14 +6568,14 @@ msgstr "" "`u8` 值,而使用后 8 位来存储第二个 `u8` 值。这样,我们就可以在单个 felt 值中存" "储两个 `u8` 值。" -#: src/ch02/optimisations/store_using_packing.md:15 +#: src/advanced-concepts/optimisations/store_using_packing.md:15 msgid "" "Cairo provides a built-in store using packing that you can use with the " "`StorePacking` trait." msgstr "" "Cairo 提供了一个内置的打包存储功能,您可以通过 `StorePacking` 特性来使用它。" -#: src/ch02/optimisations/store_using_packing.md:17 +#: src/advanced-concepts/optimisations/store_using_packing.md:17 msgid "" "```rust\n" "trait StorePacking {\n" @@ -6591,7 +6591,7 @@ msgstr "" "}\n" "```" -#: src/ch02/optimisations/store_using_packing.md:24 +#: src/advanced-concepts/optimisations/store_using_packing.md:24 msgid "" "This allows to store the type `T` by first packing it into the type `PackedT` " "with the `pack` function, and then storing the `PackedT` value with it's " @@ -6603,14 +6603,14 @@ msgstr "" "`Store` 实现来存储 `PackedT` 值。在读取值时,我们首先获取 `PackedT` 值,然后使" "用 `unpack` 函数将其解包为类型 `T`。" -#: src/ch02/optimisations/store_using_packing.md:26 +#: src/advanced-concepts/optimisations/store_using_packing.md:26 msgid "" "Here's an example of storing a `Time` struct with two `u8` values using the " "`StorePacking` trait:" msgstr "" "以下是一个使用 `StorePacking` 特性存储包含两个 `u8` 值的 `Time` 结构体的示例:" -#: src/ch02/optimisations/store_using_packing.md:28 +#: src/advanced-concepts/optimisations/store_using_packing.md:28 msgid "" "```rust\n" "#[starknet::interface]\n" @@ -6740,7 +6740,7 @@ msgstr "" "}\n" "```" -#: src/ch02/optimisations/store_using_packing.md:88 +#: src/advanced-concepts/optimisations/store_using_packing.md:88 msgid "" "Play with this contract in [Remix](https://remix.ethereum.org/?" "#activate=Starknet-cairo1-compiler&url=https://github.com/NethermindEth/" @@ -6752,11 +6752,11 @@ msgstr "" "listings/advanced-concepts/store_using_packing/src/contract.cairo) 上测试这个" "合约." -#: src/ch02/list.md:1 +#: src/advanced-concepts/list.md:1 msgid "# List" msgstr "# 列表" -#: src/ch02/list.md:3 +#: src/advanced-concepts/list.md:3 msgid "" "By default, there is no list type supported in Cairo, but you can use " "Alexandria. You can refer to the [Alexandria documentation](https://github." @@ -6766,15 +6766,15 @@ msgstr "" "[Alexandria 文档](https://github.com/keep-starknet-strange/alexandria/tree/" "main/src/storage) 获取更多详细信息。" -#: src/ch02/list.md:5 +#: src/advanced-concepts/list.md:5 msgid "## What is `List`?" msgstr "## `List`是什么?" -#: src/ch02/list.md:7 +#: src/advanced-concepts/list.md:7 msgid "An ordered sequence of values that can be used in Starknet storage:" msgstr "可以在 Starknet 存储中使用的有序值序列:" -#: src/ch02/list.md:9 +#: src/advanced-concepts/list.md:9 msgid "" "```rust\n" "#[storage]\n" @@ -6790,11 +6790,11 @@ msgstr "" "}\n" "```" -#: src/ch02/list.md:16 +#: src/advanced-concepts/list.md:16 msgid "### Interface" msgstr "### 接口" -#: src/ch02/list.md:18 +#: src/advanced-concepts/list.md:18 msgid "" "```rust\n" "trait ListTrait {\n" @@ -6820,14 +6820,14 @@ msgstr "" "}\n" "```" -#: src/ch02/list.md:30 +#: src/advanced-concepts/list.md:30 msgid "" "`List` also implements `IndexView` so you can use the familiar bracket " "notation to access its members:" msgstr "" "`List` 还实现了 `IndexView`,因此您可以使用熟悉的方括号表示法来访问其成员:" -#: src/ch02/list.md:32 +#: src/advanced-concepts/list.md:32 msgid "" "```rust\n" "let second = self.amounts.read()[1];\n" @@ -6837,7 +6837,7 @@ msgstr "" "let second = self.amounts.read()[1];\n" "```" -#: src/ch02/list.md:36 +#: src/advanced-concepts/list.md:36 msgid "" "Note that unlike `get`, using this bracket notation panics when accessing an " "out of bounds index." @@ -6845,11 +6845,11 @@ msgstr "" "请注意,与 `get` 不同的是,使用这种方括号表示法在访问越界索引时会引发 panic" "(崩溃)。" -#: src/ch02/list.md:38 +#: src/advanced-concepts/list.md:38 msgid "### Support for custom types" msgstr "### 支持自定义类型" -#: src/ch02/list.md:40 +#: src/advanced-concepts/list.md:40 msgid "" "`List` supports most of the corelib types out of the box. If you want to " "store a your own custom type in a `List`, it has to implement the `Store` " @@ -6860,15 +6860,15 @@ msgstr "" "该类型必须实现 `Store` 特性。您可以使用 `#[derive(starknet::Store)]` 属性让编" "译器自动生成。" -#: src/ch02/list.md:42 +#: src/advanced-concepts/list.md:42 msgid "### Caveats" msgstr "### 注意事项" -#: src/ch02/list.md:44 +#: src/advanced-concepts/list.md:44 msgid "There are two idiosyncacies you should be aware of when using `List`" msgstr "在使用 `List` 时,有两个特点应该注意:" -#: src/ch02/list.md:46 +#: src/advanced-concepts/list.md:46 msgid "" "1. The `append` operation costs 2 storage writes - one for the value itself " "and another one for updating the List's length\n" @@ -6881,7 +6881,7 @@ msgstr "" "2. 由于编译器的限制,不能使用单个内联语句进行变更操作。例如,`self.amounts." "read().append(42);` 是不行的。你必须分两步进行:" -#: src/ch02/list.md:49 +#: src/advanced-concepts/list.md:49 msgid "" "```rust\n" "let mut amounts = self.amounts.read();\n" @@ -6893,15 +6893,15 @@ msgstr "" "amounts.append(42);\n" "```" -#: src/ch02/list.md:54 +#: src/advanced-concepts/list.md:54 msgid "### Dependencies" msgstr "### 依赖关系" -#: src/ch02/list.md:56 +#: src/advanced-concepts/list.md:56 msgid "Update your project dependencies by in the `Scarb.toml` file:" msgstr "在 `Scarb.toml` 里更新您项目的依赖:" -#: src/ch02/list.md:57 +#: src/advanced-concepts/list.md:57 msgid "" "```rust\n" "[dependencies]\n" @@ -6917,13 +6917,13 @@ msgstr "" "alexandria.git\" }\n" "```" -#: src/ch02/list.md:63 +#: src/advanced-concepts/list.md:63 msgid "" "For example, let's use `List` to create a contract that tracks a list of " "amounts and tasks:" msgstr "例如,我们用 `List` 来创建一个跟踪`amount`和`tasks`的合约:" -#: src/ch02/list.md:65 +#: src/advanced-concepts/list.md:65 msgid "" "```rust\n" "#[starknet::interface]\n" diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 955beabe..10436298 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -2,44 +2,44 @@ Summary [Introduction](./starknet-by-example.md) - + # Getting Started - + -- [Basics of a Starknet contract](./ch00/basics/introduction.md) - - [Storage](./ch00/basics/storage.md) - - [Constructor](./ch00/basics/constructor.md) - - [Variables](./ch00/basics/variables.md) - - [Visibility and Mutability](./ch00/basics/visibility-mutability.md) - - [Counter Example](./ch00/basics/counter.md) - - [Mappings](./ch00/basics/mappings.md) - - [Errors](./ch00/basics/errors.md) - - [Events](./ch00/basics/events.md) - - [Syscalls](./ch00/basics/syscalls.md) - - [Strings and ByteArrays](./ch00/basics/bytearrays-strings.md) - - [Storing Custom Types](./ch00/basics/storing-custom-types.md) - - [Custom types in entrypoints](./ch00/basics/custom-types-in-entrypoints.md) - - [Documentation](./ch00/basics/documentation.md) -- [Deploy and interact with contracts](./ch00/interacting/interacting.md) - - [Contract interfaces and Traits generation](./ch00/interacting/interfaces-traits.md) - - [Calling other contracts](./ch00/interacting/calling_other_contracts.md) - - [Factory pattern](./ch00/interacting/factory.md) -- [Testing contracts](./ch00/testing/contract-testing.md) -- [Cairo cheatsheet](./ch00/cairo_cheatsheet/cairo_cheatsheet.md) - - [Felt](./ch00/cairo_cheatsheet/felt.md) - - [LegacyMap](./ch00/cairo_cheatsheet/mapping.md) - - [Arrays](./ch00/cairo_cheatsheet/arrays.md) - - [loop](./ch00/cairo_cheatsheet/loop.md) - - [while](./ch00/cairo_cheatsheet/while.md) - - [if let](./ch00/cairo_cheatsheet/if_let.md) - - [while let](./ch00/cairo_cheatsheet/while_let.md) - - [Enums](./ch00/cairo_cheatsheet/enums.md) - - [Match](./ch00/cairo_cheatsheet/match.md) - - [Tuples](./ch00/cairo_cheatsheet/tuples.md) - - [Struct](./ch00/cairo_cheatsheet/struct.md) - - [Type casting](./ch00/cairo_cheatsheet/type_casting.md) +- [Basics of a Starknet contract](./getting-started/basics/introduction.md) + - [Storage](./getting-started/basics/storage.md) + - [Constructor](./getting-started/basics/constructor.md) + - [Variables](./getting-started/basics/variables.md) + - [Visibility and Mutability](./getting-started/basics/visibility-mutability.md) + - [Counter Example](./getting-started/basics/counter.md) + - [Mappings](./getting-started/basics/mappings.md) + - [Errors](./getting-started/basics/errors.md) + - [Events](./getting-started/basics/events.md) + - [Syscalls](./getting-started/basics/syscalls.md) + - [Strings and ByteArrays](./getting-started/basics/bytearrays-strings.md) + - [Storing Custom Types](./getting-started/basics/storing-custom-types.md) + - [Custom types in entrypoints](./getting-started/basics/custom-types-in-entrypoints.md) + - [Documentation](./getting-started/basics/documentation.md) +- [Deploy and interact with contracts](./getting-started/interacting/interacting.md) + - [Contract interfaces and Traits generation](./getting-started/interacting/interfaces-traits.md) + - [Calling other contracts](./getting-started/interacting/calling_other_contracts.md) + - [Factory pattern](./getting-started/interacting/factory.md) +- [Testing contracts](./getting-started/testing/contract-testing.md) +- [Cairo cheatsheet](./getting-started/cairo_cheatsheet/cairo_cheatsheet.md) + - [Felt](./getting-started/cairo_cheatsheet/felt.md) + - [LegacyMap](./getting-started/cairo_cheatsheet/mapping.md) + - [Arrays](./getting-started/cairo_cheatsheet/arrays.md) + - [loop](./getting-started/cairo_cheatsheet/loop.md) + - [while](./getting-started/cairo_cheatsheet/while.md) + - [if let](./getting-started/cairo_cheatsheet/if_let.md) + - [while let](./getting-started/cairo_cheatsheet/while_let.md) + - [Enums](./getting-started/cairo_cheatsheet/enums.md) + - [Match](./getting-started/cairo_cheatsheet/match.md) + - [Tuples](./getting-started/cairo_cheatsheet/tuples.md) + - [Struct](./getting-started/cairo_cheatsheet/struct.md) + - [Type casting](./getting-started/cairo_cheatsheet/type_casting.md) # Components @@ -48,31 +48,30 @@ Summary - [Storage Collisions](./components/collisions.md) - [Ownable](./components/ownable.md) - + # Applications -- [Upgradeable Contract](./ch01/upgradeable_contract.md) -- [Defi Vault](./ch01/simple_vault.md) -- [ERC20 Token](./ch01/erc20.md) -- [NFT Dutch Auction](./ch01/nft_dutch_auction.md) -- [Constant Product AMM](./ch01/constant-product-amm.md) -- [TimeLock](./ch01/timelock.md) -- [Staking](./ch01/staking.md) +- [Upgradeable Contract](./applications/upgradeable_contract.md) +- [Defi Vault](./applications/simple_vault.md) +- [ERC20 Token](./applications/erc20.md) +- [Constant Product AMM](./applications/constant-product-amm.md) +- [TimeLock](./applications/timelock.md) +- [Staking](./applications/staking.md) - + # Advanced concepts -- [Writing to any storage slot](./ch02/write_to_any_slot.md) -- [Storing Arrays](./ch02/storing_arrays.md) -- [Struct as mapping key](./ch02/struct-mapping-key.md) -- [Hashing](./ch02/hashing.md) +- [Writing to any storage slot](./advanced-concepts/write_to_any_slot.md) +- [Storing Arrays](./advanced-concepts/storing_arrays.md) +- [Struct as mapping key](./advanced-concepts/struct-mapping-key.md) +- [Hashing](./advanced-concepts/hashing.md) - -- [Optimisations](./ch02/optimisations/optimisations.md) - - [Storage Optimisations](./ch02/optimisations/store_using_packing.md) -- [List](./ch02/list.md) -- [Plugins](./ch02/plugins.md) -- [Signature Verification](./ch02/signature_verification.md) -- [Library Calls](./ch02/library_calls.md) + +- [Optimisations](./advanced-concepts/optimisations/optimisations.md) + - [Storage Optimisations](./advanced-concepts/optimisations/store_using_packing.md) +- [List](./advanced-concepts/list.md) +- [Plugins](./advanced-concepts/plugins.md) +- [Signature Verification](./advanced-concepts/signature_verification.md) +- [Library Calls](./advanced-concepts/library_calls.md) diff --git a/src/ch02/hash-solidity-compatible.md b/src/advanced-concepts/hash-solidity-compatible.md similarity index 100% rename from src/ch02/hash-solidity-compatible.md rename to src/advanced-concepts/hash-solidity-compatible.md diff --git a/src/ch02/hashing.md b/src/advanced-concepts/hashing.md similarity index 100% rename from src/ch02/hashing.md rename to src/advanced-concepts/hashing.md diff --git a/src/ch02/library_calls.md b/src/advanced-concepts/library_calls.md similarity index 100% rename from src/ch02/library_calls.md rename to src/advanced-concepts/library_calls.md diff --git a/src/ch02/list.md b/src/advanced-concepts/list.md similarity index 100% rename from src/ch02/list.md rename to src/advanced-concepts/list.md diff --git a/src/ch02/optimisations/optimisations.md b/src/advanced-concepts/optimisations/optimisations.md similarity index 100% rename from src/ch02/optimisations/optimisations.md rename to src/advanced-concepts/optimisations/optimisations.md diff --git a/src/ch02/optimisations/store_using_packing.md b/src/advanced-concepts/optimisations/store_using_packing.md similarity index 100% rename from src/ch02/optimisations/store_using_packing.md rename to src/advanced-concepts/optimisations/store_using_packing.md diff --git a/src/ch02/plugins.md b/src/advanced-concepts/plugins.md similarity index 100% rename from src/ch02/plugins.md rename to src/advanced-concepts/plugins.md diff --git a/src/ch02/signature_verification.md b/src/advanced-concepts/signature_verification.md similarity index 100% rename from src/ch02/signature_verification.md rename to src/advanced-concepts/signature_verification.md diff --git a/src/ch02/storing_arrays.md b/src/advanced-concepts/storing_arrays.md similarity index 87% rename from src/ch02/storing_arrays.md rename to src/advanced-concepts/storing_arrays.md index d980a7f4..b819dde9 100644 --- a/src/ch02/storing_arrays.md +++ b/src/advanced-concepts/storing_arrays.md @@ -2,7 +2,7 @@ On Starknet, complex values (e.g. tuples or structs) are stored in a continuous segment starting from the address of the storage variable. There is a limitation in Cairo that restricts complex storage values to a maximum of 256 field elements. This means that to store arrays with more than 255 elements, you would have to split them into segments of size `n <= 255` and store these segments at multiple storage addresses. There is currently no native support for storing arrays in Cairo, so you would need to write your own implementation of the `Store` trait for the array type you wish to store. -However, the `ByteArray` struct can be used to store `Array` in storage without additional implementation. Before implementing your own `Store` trait, consider whether the `ByteArray` struct can be used to store the data you need! See the [ByteArray](../ch00/basics/bytearrays-strings.md#bytearray-long-strings) section for more information. +However, the `ByteArray` struct can be used to store `Array` in storage without additional implementation. Before implementing your own `Store` trait, consider wether the `ByteArray` struct can be used to store the data you need! See the [ByteArray](../getting-started/basics/bytearrays-strings.md#bytearray-long-strings) section for more information. > Note: While storing arrays in storage is possible, it is not always recommended, as the read and write operations can get very costly. For example, reading an array of size `n` requires `n` storage reads, and writing to an array of size `n` requires `n` storage writes. If you only need to access a single element of the array at a time, it is recommended to use a `LegacyMap` and store the length in another variable instead. diff --git a/src/ch02/struct-mapping-key.md b/src/advanced-concepts/struct-mapping-key.md similarity index 100% rename from src/ch02/struct-mapping-key.md rename to src/advanced-concepts/struct-mapping-key.md diff --git a/src/ch02/write_to_any_slot.md b/src/advanced-concepts/write_to_any_slot.md similarity index 100% rename from src/ch02/write_to_any_slot.md rename to src/advanced-concepts/write_to_any_slot.md diff --git a/src/ch01/constant-product-amm.md b/src/applications/constant-product-amm.md similarity index 100% rename from src/ch01/constant-product-amm.md rename to src/applications/constant-product-amm.md diff --git a/src/ch01/erc20.md b/src/applications/erc20.md similarity index 100% rename from src/ch01/erc20.md rename to src/applications/erc20.md diff --git a/src/ch01/nft_dutch_auction.md b/src/applications/nft_dutch_auction.md similarity index 100% rename from src/ch01/nft_dutch_auction.md rename to src/applications/nft_dutch_auction.md diff --git a/src/ch01/signature_verification.md b/src/applications/signature_verification.md similarity index 100% rename from src/ch01/signature_verification.md rename to src/applications/signature_verification.md diff --git a/src/ch01/simple_vault.md b/src/applications/simple_vault.md similarity index 100% rename from src/ch01/simple_vault.md rename to src/applications/simple_vault.md diff --git a/src/ch01/staking.md b/src/applications/staking.md similarity index 100% rename from src/ch01/staking.md rename to src/applications/staking.md diff --git a/src/ch01/timelock.md b/src/applications/timelock.md similarity index 100% rename from src/ch01/timelock.md rename to src/applications/timelock.md diff --git a/src/ch01/upgradeable_contract.md b/src/applications/upgradeable_contract.md similarity index 100% rename from src/ch01/upgradeable_contract.md rename to src/applications/upgradeable_contract.md diff --git a/src/ch00/basics/bytearrays-strings.md b/src/getting-started/basics/bytearrays-strings.md similarity index 100% rename from src/ch00/basics/bytearrays-strings.md rename to src/getting-started/basics/bytearrays-strings.md diff --git a/src/ch00/basics/constructor.md b/src/getting-started/basics/constructor.md similarity index 100% rename from src/ch00/basics/constructor.md rename to src/getting-started/basics/constructor.md diff --git a/src/ch00/basics/counter.md b/src/getting-started/basics/counter.md similarity index 100% rename from src/ch00/basics/counter.md rename to src/getting-started/basics/counter.md diff --git a/src/ch00/basics/custom-types-in-entrypoints.md b/src/getting-started/basics/custom-types-in-entrypoints.md similarity index 100% rename from src/ch00/basics/custom-types-in-entrypoints.md rename to src/getting-started/basics/custom-types-in-entrypoints.md diff --git a/src/ch00/basics/documentation.md b/src/getting-started/basics/documentation.md similarity index 100% rename from src/ch00/basics/documentation.md rename to src/getting-started/basics/documentation.md diff --git a/src/ch00/basics/errors.md b/src/getting-started/basics/errors.md similarity index 100% rename from src/ch00/basics/errors.md rename to src/getting-started/basics/errors.md diff --git a/src/ch00/basics/events.md b/src/getting-started/basics/events.md similarity index 100% rename from src/ch00/basics/events.md rename to src/getting-started/basics/events.md diff --git a/src/ch00/basics/introduction.md b/src/getting-started/basics/introduction.md similarity index 100% rename from src/ch00/basics/introduction.md rename to src/getting-started/basics/introduction.md diff --git a/src/ch00/basics/mappings.md b/src/getting-started/basics/mappings.md similarity index 100% rename from src/ch00/basics/mappings.md rename to src/getting-started/basics/mappings.md diff --git a/src/ch00/basics/storage.md b/src/getting-started/basics/storage.md similarity index 100% rename from src/ch00/basics/storage.md rename to src/getting-started/basics/storage.md diff --git a/src/ch00/basics/storing-custom-types.md b/src/getting-started/basics/storing-custom-types.md similarity index 100% rename from src/ch00/basics/storing-custom-types.md rename to src/getting-started/basics/storing-custom-types.md diff --git a/src/ch00/basics/syscalls.md b/src/getting-started/basics/syscalls.md similarity index 99% rename from src/ch00/basics/syscalls.md rename to src/getting-started/basics/syscalls.md index cddc5e7f..e79a4468 100644 --- a/src/ch00/basics/syscalls.md +++ b/src/getting-started/basics/syscalls.md @@ -201,7 +201,7 @@ fn replace_class_syscall( Replace the class of the calling contract with the class `class_hash`. -This is used for contract upgrades. Here's an example from the [Upgradeable Contract](../../ch01/upgradeable_contract.md): +This is used for contract upgrades. Here's an example from the [Upgradeable Contract](../../applications/upgradeable_contract.md): ```rust {{#rustdoc_include ../../../listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo:upgrade}} diff --git a/src/ch00/basics/variables.md b/src/getting-started/basics/variables.md similarity index 100% rename from src/ch00/basics/variables.md rename to src/getting-started/basics/variables.md diff --git a/src/ch00/basics/visibility-mutability.md b/src/getting-started/basics/visibility-mutability.md similarity index 100% rename from src/ch00/basics/visibility-mutability.md rename to src/getting-started/basics/visibility-mutability.md diff --git a/src/ch00/cairo_cheatsheet/arrays.md b/src/getting-started/cairo_cheatsheet/arrays.md similarity index 100% rename from src/ch00/cairo_cheatsheet/arrays.md rename to src/getting-started/cairo_cheatsheet/arrays.md diff --git a/src/ch00/cairo_cheatsheet/cairo_cheatsheet.md b/src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md similarity index 100% rename from src/ch00/cairo_cheatsheet/cairo_cheatsheet.md rename to src/getting-started/cairo_cheatsheet/cairo_cheatsheet.md diff --git a/src/ch00/cairo_cheatsheet/enums.md b/src/getting-started/cairo_cheatsheet/enums.md similarity index 100% rename from src/ch00/cairo_cheatsheet/enums.md rename to src/getting-started/cairo_cheatsheet/enums.md diff --git a/src/ch00/cairo_cheatsheet/felt.md b/src/getting-started/cairo_cheatsheet/felt.md similarity index 100% rename from src/ch00/cairo_cheatsheet/felt.md rename to src/getting-started/cairo_cheatsheet/felt.md diff --git a/src/ch00/cairo_cheatsheet/if_let.md b/src/getting-started/cairo_cheatsheet/if_let.md similarity index 100% rename from src/ch00/cairo_cheatsheet/if_let.md rename to src/getting-started/cairo_cheatsheet/if_let.md diff --git a/src/ch00/cairo_cheatsheet/loop.md b/src/getting-started/cairo_cheatsheet/loop.md similarity index 100% rename from src/ch00/cairo_cheatsheet/loop.md rename to src/getting-started/cairo_cheatsheet/loop.md diff --git a/src/ch00/cairo_cheatsheet/mapping.md b/src/getting-started/cairo_cheatsheet/mapping.md similarity index 100% rename from src/ch00/cairo_cheatsheet/mapping.md rename to src/getting-started/cairo_cheatsheet/mapping.md diff --git a/src/ch00/cairo_cheatsheet/match.md b/src/getting-started/cairo_cheatsheet/match.md similarity index 100% rename from src/ch00/cairo_cheatsheet/match.md rename to src/getting-started/cairo_cheatsheet/match.md diff --git a/src/ch00/cairo_cheatsheet/struct.md b/src/getting-started/cairo_cheatsheet/struct.md similarity index 100% rename from src/ch00/cairo_cheatsheet/struct.md rename to src/getting-started/cairo_cheatsheet/struct.md diff --git a/src/ch00/cairo_cheatsheet/tuples.md b/src/getting-started/cairo_cheatsheet/tuples.md similarity index 100% rename from src/ch00/cairo_cheatsheet/tuples.md rename to src/getting-started/cairo_cheatsheet/tuples.md diff --git a/src/ch00/cairo_cheatsheet/type_casting.md b/src/getting-started/cairo_cheatsheet/type_casting.md similarity index 100% rename from src/ch00/cairo_cheatsheet/type_casting.md rename to src/getting-started/cairo_cheatsheet/type_casting.md diff --git a/src/ch00/cairo_cheatsheet/while.md b/src/getting-started/cairo_cheatsheet/while.md similarity index 100% rename from src/ch00/cairo_cheatsheet/while.md rename to src/getting-started/cairo_cheatsheet/while.md diff --git a/src/ch00/cairo_cheatsheet/while_let.md b/src/getting-started/cairo_cheatsheet/while_let.md similarity index 100% rename from src/ch00/cairo_cheatsheet/while_let.md rename to src/getting-started/cairo_cheatsheet/while_let.md diff --git a/src/ch00/env_setup.md b/src/getting-started/env_setup.md similarity index 100% rename from src/ch00/env_setup.md rename to src/getting-started/env_setup.md diff --git a/src/ch00/interacting/calling_other_contracts.md b/src/getting-started/interacting/calling_other_contracts.md similarity index 100% rename from src/ch00/interacting/calling_other_contracts.md rename to src/getting-started/interacting/calling_other_contracts.md diff --git a/src/ch00/interacting/factory.md b/src/getting-started/interacting/factory.md similarity index 100% rename from src/ch00/interacting/factory.md rename to src/getting-started/interacting/factory.md diff --git a/src/ch00/interacting/interacting.md b/src/getting-started/interacting/interacting.md similarity index 100% rename from src/ch00/interacting/interacting.md rename to src/getting-started/interacting/interacting.md diff --git a/src/ch00/interacting/interfaces-traits.md b/src/getting-started/interacting/interfaces-traits.md similarity index 100% rename from src/ch00/interacting/interfaces-traits.md rename to src/getting-started/interacting/interfaces-traits.md diff --git a/src/ch00/testing/contract-testing.md b/src/getting-started/testing/contract-testing.md similarity index 100% rename from src/ch00/testing/contract-testing.md rename to src/getting-started/testing/contract-testing.md From 0d9f47354c3a5704ec9d6e7c4f680e5a6783b8b2 Mon Sep 17 00:00:00 2001 From: Ege Date: Tue, 25 Jun 2024 04:40:29 +0300 Subject: [PATCH 19/36] feat: simple storage with starknet-js (#222) * feat: simple storage with starknet-js * feat: add how_to_deploy & fix tutorial content for simple_storage * fix: update links & add section in summary * feat: revisions #222 --------- Co-authored-by: julio4 --- Scarb.lock | 4 + .../simple_storage_starknetjs/.env.example | 1 + .../simple_storage_starknetjs/.gitignore | 1 + .../simple_storage_starknetjs/Scarb.lock | 6 + .../simple_storage_starknetjs/Scarb.toml | 14 + .../simple_storage_starknetjs/abi.json | 616 ++++++++++++++++++ .../simple_storage_starknetjs/index.js | 49 ++ .../simple_storage_starknetjs/src/lib.cairo | 1 + .../src/storage.cairo | 28 + src/SUMMARY.md | 2 + src/applications/simple_storage_starknetjs.md | 100 +++ .../interacting/how_to_deploy.md | 79 +++ 12 files changed, 901 insertions(+) create mode 100644 listings/applications/simple_storage_starknetjs/.env.example create mode 100644 listings/applications/simple_storage_starknetjs/.gitignore create mode 100644 listings/applications/simple_storage_starknetjs/Scarb.lock create mode 100644 listings/applications/simple_storage_starknetjs/Scarb.toml create mode 100644 listings/applications/simple_storage_starknetjs/abi.json create mode 100644 listings/applications/simple_storage_starknetjs/index.js create mode 100644 listings/applications/simple_storage_starknetjs/src/lib.cairo create mode 100644 listings/applications/simple_storage_starknetjs/src/storage.cairo create mode 100644 src/applications/simple_storage_starknetjs.md create mode 100644 src/getting-started/interacting/how_to_deploy.md diff --git a/Scarb.lock b/Scarb.lock index ef3d064e..dcae197b 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -105,6 +105,10 @@ source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.11.0#a8 name = "scarb" version = "0.1.0" +[[package]] +name = "simple_storage" +version = "0.1.0" + [[package]] name = "simple_vault" version = "0.1.0" diff --git a/listings/applications/simple_storage_starknetjs/.env.example b/listings/applications/simple_storage_starknetjs/.env.example new file mode 100644 index 00000000..32869cab --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/.env.example @@ -0,0 +1 @@ +PRIVATE_KEY = "PASTE_PRIVATE_KEY_HERE" \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/.gitignore b/listings/applications/simple_storage_starknetjs/.gitignore new file mode 100644 index 00000000..2eea525d --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/.gitignore @@ -0,0 +1 @@ +.env \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/Scarb.lock b/listings/applications/simple_storage_starknetjs/Scarb.lock new file mode 100644 index 00000000..d9817fa8 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "simple_storage" +version = "0.1.0" diff --git a/listings/applications/simple_storage_starknetjs/Scarb.toml b/listings/applications/simple_storage_starknetjs/Scarb.toml new file mode 100644 index 00000000..f22b7903 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "simple_storage" +version.workspace = true +edition = "2023_11" + +[lib] + +[dependencies] +starknet.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/abi.json b/listings/applications/simple_storage_starknetjs/abi.json new file mode 100644 index 00000000..761ae6c2 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/abi.json @@ -0,0 +1,616 @@ +{ + "sierra_program": [ + "0x1", + "0x5", + "0x0", + "0x2", + "0x6", + "0x3", + "0x98", + "0x68", + "0x18", + "0x52616e6765436865636b", + "0x800000000000000100000000000000000000000000000000", + "0x436f6e7374", + "0x800000000000000000000000000000000000000000000002", + "0x1", + "0x16", + "0x2", + "0x53746f726555313238202d206e6f6e2075313238", + "0x4661696c656420746f20646573657269616c697a6520706172616d202331", + "0x4f7574206f6620676173", + "0x4172726179", + "0x800000000000000300000000000000000000000000000001", + "0x536e617073686f74", + "0x800000000000000700000000000000000000000000000001", + "0x4", + "0x537472756374", + "0x800000000000000700000000000000000000000000000002", + "0x0", + "0x1baeba72e79e9db2587cf44fedb2f3700b2075a5e8e39a562584862c4b71f62", + "0x5", + "0x2ee1e2b1b89f8c495f200e4956278a4d47395fe262f27b52e5865c9524c08c3", + "0x6", + "0x9", + "0x753332", + "0x800000000000000700000000000000000000000000000000", + "0x53746f7261676541646472657373", + "0x53746f726167654261736541646472657373", + "0x4275696c74696e436f737473", + "0x53797374656d", + "0x800000000000000f00000000000000000000000000000001", + "0x16a4c8d7c05909052238a862d8cc3e7975bf05a07b3a69c6b28951083a6d672", + "0x800000000000000300000000000000000000000000000003", + "0xe", + "0x456e756d", + "0x9931c641b913035ae674b400b61a51476d506bbe8bba2ff8a6272790aba9e6", + "0x7", + "0xf", + "0x496e70757420746f6f206c6f6e6720666f7220617267756d656e7473", + "0x75313238", + "0x426f78", + "0x800000000000000700000000000000000000000000000003", + "0x29d7d57c04a880978e7b3689f6218e507f3be17588744b58dc17762447ad0e7", + "0x14", + "0x13", + "0x66656c74323532", + "0x4761734275696c74696e", + "0x35", + "0x7265766f6b655f61705f747261636b696e67", + "0x77697468647261775f676173", + "0x6272616e63685f616c69676e", + "0x7374727563745f6465636f6e737472756374", + "0x656e61626c655f61705f747261636b696e67", + "0x73746f72655f74656d70", + "0x61727261795f736e617073686f745f706f705f66726f6e74", + "0x656e756d5f696e6974", + "0x15", + "0x6a756d70", + "0x7374727563745f636f6e737472756374", + "0x656e756d5f6d61746368", + "0x756e626f78", + "0x72656e616d65", + "0x75313238735f66726f6d5f66656c74323532", + "0x64697361626c655f61705f747261636b696e67", + "0x64726f70", + "0x12", + "0x61727261795f6e6577", + "0x636f6e73745f61735f696d6d656469617465", + "0x11", + "0x61727261795f617070656e64", + "0x10", + "0x17", + "0xd", + "0x6765745f6275696c74696e5f636f737473", + "0xc", + "0x77697468647261775f6761735f616c6c", + "0x73746f726167655f626173655f616464726573735f636f6e7374", + "0x3f21dadc69f28434fd7a4035b4cee8d901bf7ef13138a70c3f754bf85806657", + "0x753132385f746f5f66656c74323532", + "0x73746f726167655f616464726573735f66726f6d5f62617365", + "0x8", + "0xa", + "0x73746f726167655f77726974655f73797363616c6c", + "0x736e617073686f745f74616b65", + "0x3", + "0x73746f726167655f726561645f73797363616c6c", + "0xeb", + "0xffffffffffffffff", + "0x74", + "0xb", + "0x63", + "0x5d", + "0x19", + "0x1a", + "0x1b", + "0x2b", + "0x1c", + "0x1d", + "0x1e", + "0x1f", + "0x20", + "0x21", + "0x22", + "0x23", + "0x24", + "0x25", + "0x4f", + "0x26", + "0x27", + "0x28", + "0x29", + "0x2a", + "0x2c", + "0x2d", + "0x46", + "0x2e", + "0x2f", + "0x30", + "0x31", + "0x32", + "0x33", + "0x34", + "0x36", + "0x37", + "0x38", + "0x39", + "0x3a", + "0x3b", + "0x3c", + "0x3d", + "0x3e", + "0x3f", + "0x40", + "0x67", + "0x41", + "0x42", + "0x43", + "0x44", + "0x45", + "0x47", + "0x48", + "0x49", + "0x4a", + "0x4b", + "0x4c", + "0xdd", + "0x97", + "0xd0", + "0xc3", + "0xb7", + "0xc8", + "0x82", + "0x876", + "0x11100f050e0d06050c0b0a0706050403090706050403080706050403020100", + "0x101f121e10021d191c191b191a191812071705040316051512111014051312", + "0x6050e2815121e10192726070605040325052405231220220f052105151220", + "0x536120505351234160505331232123112302f022e192d2c052b052a122922", + "0x5053b0507380507372c05053a123938050535140505351207380507370605", + "0x542410505402b05054014050540123f123e060505350605053d0605053c38", + "0x50535470505350507460507372505053a2105053a06050545440505430605", + "0x5351705053550050543124f124e4d07054c124b4a05053512494605053548", + "0x40540505431207460507372405053a1605053a0f0505400f05055312525105", + "0x70512125705121212560f05053505050543125516050540070505432c0505", + "0x125705120f12500557055405541212570512071224160758140f0757070512", + "0x55105241212570512071259054a5117075707500516120f0557050f051412", + "0x125705120712124405125112210557054a05171248055705170550124a0557", + "0x210557054605171248055705590550124605570525054a1225055705125912", + "0x57052c0525122c0557054405211212570512071241055a4405570721054812", + "0x1257051207125c0047545b06380757072b0f0744122b0557052b0546122b05", + "0x570512411212570512071260055f5e5d075707480516123805570538051412", + "0x471261055705120612125705060538121257055e052b121257055d052c1212", + "0x5d1264055705125c1263055705626107001262055705620546126205570512", + "0x140557051405601238055705380514126605570565055e1265055705636407", + "0x124112125705120712660714380f0566055705660562120705570507056112", + "0x67143854651267055705670564126705570512631212570560052c12125705", + "0x126e055705060567126d0557051266121257051207126c6b076a6968075707", + "0x126f0557056f056c125f0557055f056b125f0557051269126f0557056d0568", + "0x125705120712757473547271700757076e6f5f0769146d1268055705680514", + "0x7905570578055f1212570577056f12787707570576056e1276055705120612", + "0x557057005601268055705680514127a0557052f0571122f05570579057012", + "0x5c121257051207127a7170680f057a0557057a056212710557057105611270", + "0x1268055705680514127d0557057c055e127c055705757b075d127b05570512", + "0x7127d7473680f057d0557057d056212740557057405611273055705730560", + "0x57057f0546127f0557051273127e0557051206121257050605381212570512", + "0x82055e12820557058081075d1281055705125c12800557057f7e0700127f05", + "0x5621207055705070561126c0557056c0560126b0557056b05141283055705", + "0x1257055c0538121257050005381212570512071283076c6b0f058305570583", + "0x41057412125705120712128505125112840557054705141212570548052c12", + "0x86055705120612125705124112840557050f05141212570548052c12125705", + "0x89055705125c12880557058786070012870557058705461287055705127512", + "0x5705140560128405570584051412720557058a055e128a0557058889075d12", + "0x12125705120712720714840f05720557057205621207055705070561121405", + "0x700128c0557058c0546128c0557051273128b055705120612125705540576", + "0x12900557058f055e128f0557058d8e075d128e055705125c128d0557058c8b", + "0x90055705900562120705570507056112240557052405601216055705160514", + "0x570512071224160791140f0757070512070512125705121212900724160f05", + "0x7125905925117075707500516120f0557050f051412500557055405541212", + "0x557051247124a05570512061212570551052b1212570517052c1212570512", + "0x52125075d1225055705125c1221055705484a070012480557054805461248", + "0x705611214055705140560120f0557050f0514124405570546055e12460557", + "0x12570559052c121257051207124407140f0f05440557054405621207055705", + "0x12063807932b2c07570741140f546512410557054105641241055705126312", + "0x5c056b125c0557051269120005570547056812470557051266121257051207", + "0x5d545707005c072b0f77122c0557052c0514120005570500056c125c055705", + "0x61125d0557055d05601260055705600546121257051207126362615494605e", + "0x512061212570512071268676654956564075707602c0744125e0557055e05", + "0x6f126e6d0757056c056e126c0557056b690700126b05570565056712690557", + "0x1412700557055f0571125f0557056f0570126f0557056e055f121257056d05", + "0x570055705700562125e0557055e0561125d0557055d056012640557056405", + "0x557051206121257056805381212570567053812125705120712705e5d640f", + "0x57056605141274055705737107001273055705730546127305570512781271", + "0x96051251127805570574057912770557055e056112760557055d0560127505", + "0x77055705620561127605570561056012750557052c05141212570512071212", + "0x557052f055e122f0557057879075d1279055705125c127805570563057912", + "0x57057a0562127705570577056112760557057605601275055705750514127a", + "0x546127c0557051273127b0557051206121257051207127a7776750f057a05", + "0x127f0557057d7e075d127e055705125c127d0557057c7b0700127c0557057c", + "0x70557050705611206055705060560123805570538051412800557057f055e", + "0x512061212570554057612125705120712800706380f058005570580056212", + "0x5125c12830557058281070012820557058205461282055705127312810557", + "0x5601216055705160514128705570586055e12860557058384075d12840557", + "0x47120f07870724160f05870557058705621207055705070561122405570524", + "0x9754070512464847120f164847120f1254070512464847120f1648" + ], + "sierra_program_debug_info": { + "type_names": [ + [ + 0, + "RangeCheck" + ], + [ + 1, + "Const" + ], + [ + 2, + "Const" + ], + [ + 3, + "Const" + ], + [ + 4, + "Array" + ], + [ + 5, + "Snapshot>" + ], + [ + 6, + "core::array::Span::" + ], + [ + 7, + "Tuple>" + ], + [ + 8, + "Const" + ], + [ + 9, + "u32" + ], + [ + 10, + "StorageAddress" + ], + [ + 11, + "StorageBaseAddress" + ], + [ + 12, + "BuiltinCosts" + ], + [ + 13, + "System" + ], + [ + 14, + "core::panics::Panic" + ], + [ + 15, + "Tuple>" + ], + [ + 16, + "core::panics::PanicResult::<(core::array::Span::,)>" + ], + [ + 17, + "Const" + ], + [ + 18, + "u128" + ], + [ + 19, + "Unit" + ], + [ + 20, + "Box" + ], + [ + 21, + "core::option::Option::>" + ], + [ + 22, + "felt252" + ], + [ + 23, + "GasBuiltin" + ] + ], + "libfunc_names": [ + [ + 0, + "revoke_ap_tracking" + ], + [ + 1, + "withdraw_gas" + ], + [ + 2, + "branch_align" + ], + [ + 3, + "struct_deconstruct>" + ], + [ + 4, + "enable_ap_tracking" + ], + [ + 5, + "store_temp" + ], + [ + 6, + "array_snapshot_pop_front" + ], + [ + 7, + "enum_init>, 0>" + ], + [ + 8, + "store_temp>>" + ], + [ + 9, + "store_temp>>" + ], + [ + 10, + "jump" + ], + [ + 11, + "struct_construct" + ], + [ + 12, + "enum_init>, 1>" + ], + [ + 13, + "enum_match>>" + ], + [ + 14, + "unbox" + ], + [ + 15, + "rename" + ], + [ + 16, + "store_temp" + ], + [ + 17, + "u128s_from_felt252" + ], + [ + 18, + "disable_ap_tracking" + ], + [ + 19, + "drop>>" + ], + [ + 20, + "drop>" + ], + [ + 21, + "drop" + ], + [ + 22, + "array_new" + ], + [ + 23, + "const_as_immediate>" + ], + [ + 24, + "array_append" + ], + [ + 25, + "struct_construct" + ], + [ + 26, + "struct_construct>>" + ], + [ + 27, + "enum_init,)>, 1>" + ], + [ + 28, + "store_temp" + ], + [ + 29, + "store_temp" + ], + [ + 30, + "store_temp,)>>" + ], + [ + 31, + "get_builtin_costs" + ], + [ + 32, + "store_temp" + ], + [ + 33, + "withdraw_gas_all" + ], + [ + 34, + "storage_base_address_const<1784720371058305772862806735021375459770416459932804101453333227736919991895>" + ], + [ + 35, + "u128_to_felt252" + ], + [ + 36, + "storage_address_from_base" + ], + [ + 37, + "const_as_immediate>" + ], + [ + 38, + "store_temp" + ], + [ + 39, + "store_temp" + ], + [ + 40, + "storage_write_syscall" + ], + [ + 41, + "snapshot_take>" + ], + [ + 42, + "drop>" + ], + [ + 43, + "struct_construct>" + ], + [ + 44, + "struct_construct>>" + ], + [ + 45, + "enum_init,)>, 0>" + ], + [ + 46, + "const_as_immediate>" + ], + [ + 47, + "drop" + ], + [ + 48, + "const_as_immediate>" + ], + [ + 49, + "drop>" + ], + [ + 50, + "storage_read_syscall" + ], + [ + 51, + "const_as_immediate>" + ], + [ + 52, + "store_temp>" + ] + ], + "user_func_names": [ + [ + 0, + "simple_storage::storage::SimpleStorage::__wrapper__SimpleStorage__set" + ], + [ + 1, + "simple_storage::storage::SimpleStorage::__wrapper__SimpleStorage__get" + ] + ] + }, + "contract_class_version": "0.1.0", + "entry_points_by_type": { + "EXTERNAL": [ + { + "selector": "0x17c00f03de8b5bd58d2016b59d251c13056b989171c5852949903bc043bc27", + "function_idx": 1 + }, + { + "selector": "0x2f67e6aeaad1ab7487a680eb9d3363a597afa7a3de33fa9bf3ae6edcb88435d", + "function_idx": 0 + } + ], + "L1_HANDLER": [], + "CONSTRUCTOR": [] + }, + "abi": [ + { + "type": "impl", + "name": "SimpleStorage", + "interface_name": "simple_storage::storage::ISimpleStorage" + }, + { + "type": "interface", + "name": "simple_storage::storage::ISimpleStorage", + "items": [ + { + "type": "function", + "name": "set", + "inputs": [ + { + "name": "x", + "type": "core::integer::u128" + } + ], + "outputs": [], + "state_mutability": "external" + }, + { + "type": "function", + "name": "get", + "inputs": [], + "outputs": [ + { + "type": "core::integer::u128" + } + ], + "state_mutability": "view" + } + ] + }, + { + "type": "event", + "name": "simple_storage::storage::SimpleStorage::Event", + "kind": "enum", + "variants": [] + } + ] +} \ No newline at end of file diff --git a/listings/applications/simple_storage_starknetjs/index.js b/listings/applications/simple_storage_starknetjs/index.js new file mode 100644 index 00000000..47f45c76 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/index.js @@ -0,0 +1,49 @@ +// ANCHOR: imports +import { Account, RpcProvider, json, Contract } from "starknet"; +import fs from "fs"; +import * as dotenv from "dotenv"; +dotenv.config(); +// ANCHOR_END: imports + +// ANCHOR: provider +const provider = new RpcProvider({ + nodeUrl: "https://free-rpc.nethermind.io/sepolia-juno", +}); +// ANCHOR_END: provider + +const accountAddress = + "0x067981c7F9f55BCbdD4e0d0a9C5BBCeA77dAcB42cccbf13554A847d6353F728e"; +// ANCHOR: account +const privateKey = process.env.PRIVATE_KEY; +// "1" is added to show that our account is deployed using Cairo 1.0. +const account = new Account(provider, accountAddress, privateKey, "1"); +// ANCHOR_END: account + +const contractAddress = + "0x01bb7d67375782ab08178b444dbda2b0c1c9ff4469c421124f54e1d8257f2e97"; +// ANCHOR: contract +const compiledContractAbi = json.parse( + fs.readFileSync("./abi.json").toString("ascii") +); +const storageContract = new Contract( + compiledContractAbi.abi, + contractAddress, + provider +); +// ANCHOR_END: contract + +// ANCHOR: get +let getData = await storageContract.get(); +console.log("Stored_data:", getData.toString()); +// ANCHOR_END: get + +// ANCHOR: set +storageContract.connect(account); +const myCall = storageContract.populate("set", [59]); +const res = await storageContract.set(myCall.calldata); +await provider.waitForTransaction(res.transaction_hash); + +// Get the stored data after setting it +getData = await storageContract.get(); +console.log("Stored_data after set():", getData.toString()); +// ANCHOR_END: set diff --git a/listings/applications/simple_storage_starknetjs/src/lib.cairo b/listings/applications/simple_storage_starknetjs/src/lib.cairo new file mode 100644 index 00000000..80aaac2b --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/src/lib.cairo @@ -0,0 +1 @@ +mod storage; diff --git a/listings/applications/simple_storage_starknetjs/src/storage.cairo b/listings/applications/simple_storage_starknetjs/src/storage.cairo new file mode 100644 index 00000000..38349241 --- /dev/null +++ b/listings/applications/simple_storage_starknetjs/src/storage.cairo @@ -0,0 +1,28 @@ +// ANCHOR: contract +#[starknet::interface] +trait ISimpleStorage { + fn set(ref self: T, x: u128); + fn get(self: @T) -> u128; +} + +#[starknet::contract] +mod SimpleStorage { + #[storage] + struct Storage { + stored_data: u128 + } + + #[abi(embed_v0)] + impl SimpleStorage of super::ISimpleStorage { + fn set(ref self: ContractState, x: u128) { + self.stored_data.write(x); + } + + fn get(self: @ContractState) -> u128 { + self.stored_data.read() + } + } +} +// ANCHOR_END: contract + + diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 10436298..d642f9be 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -23,6 +23,7 @@ Summary - [Custom types in entrypoints](./getting-started/basics/custom-types-in-entrypoints.md) - [Documentation](./getting-started/basics/documentation.md) - [Deploy and interact with contracts](./getting-started/interacting/interacting.md) + - [How to deploy](./getting-started/interacting/how_to_deploy.md) - [Contract interfaces and Traits generation](./getting-started/interacting/interfaces-traits.md) - [Calling other contracts](./getting-started/interacting/calling_other_contracts.md) - [Factory pattern](./getting-started/interacting/factory.md) @@ -58,6 +59,7 @@ Summary - [Constant Product AMM](./applications/constant-product-amm.md) - [TimeLock](./applications/timelock.md) - [Staking](./applications/staking.md) +- [Simple Storage with Starknet-js](./applications/simple_storage_starknetjs.md) diff --git a/src/applications/simple_storage_starknetjs.md b/src/applications/simple_storage_starknetjs.md new file mode 100644 index 00000000..db574e41 --- /dev/null +++ b/src/applications/simple_storage_starknetjs.md @@ -0,0 +1,100 @@ +# Simple Storage (Starknet-js + Cairo) + +In this example, we will use a SimpleStorage Cairo contract deployed on Starknet Sepolia Testnet and show how you can interact with the contract using Starknet-js. + +## Writing SimpleStorage contract in Cairo + +The SimpleStorage contract has only one purpose: storing a number. We want the users to interact with the stored number by **writing** to the currently stored number and **reading** the number in the contract. + +We will use the following SimpleStorage contract. In the [Storage Variables](../getting-started/basics/variables.md) page, you can find explanations for each component of the contract: + +```rs +{{#rustdoc_include ../../listings/applications/simple_storage_starknetjs/src/storage.cairo:contract}} +``` + +Because we want to interact with the get and set functions of the SimpleStorage contract using Starknet-js, we define the function signatures in `#[starknet::interface]`. The functions are defined under the macro `#[abi(embed_v0)]` where external functions are written. + +Only deployed instances of the contract can be interacted with. You can refer to the [How to Deploy](../getting-started/interacting/how_to_deploy.md) page. Note down the address of your contract, as it is needed for the following part. + +## Interacting with SimpleStorage contract + +We will interact with the SimpleStorage contract using Starknet-js. Firstly, create a new folder and inside the directory of the new folder, initialize the npm package (click Enter several items, you can skip adding the package info): + +```console +$ npm init +``` + +Now, `package.json` file is created. Change the type of the package to a module. + +```json +"type": "module" +``` + +Let's add Starknet-js as a dependency: + +```console +$ npm install starknet@next +``` + +Create a file named `index.js` where we will write JavaScript code to interact with our contract. Let's start our code by importing from Starknet-js, and from other libraries we will need: + +```js +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:imports}} +``` + +Let's create our provider object, and add our account address as a constant variable. We need the provider in order to send our queries and transactions to a Starknet node that is connected to the Starknet network: + +```js +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:provider}} +const accountAddress = // 'PASTE_ACCOUNT_ADDRESS_HERE'; +``` + +The next step is creating an `Account` object that will be used to sign transactions, so we need to import the account private key. You can access it directly from your keystore with the following command using Starkli: + +```console +$ starkli signer keystore inspect-private /path/to/starkli-wallet/keystore.json --raw +``` + +Create a `.env` file in your project folder, and paste your private key as shown in the following line: +```bash +{{#include ../../listings/applications/simple_storage_starknetjs/.env.example}} +``` + +> Warning: Using `.env` files is not recommended for production environments, please use `.env` files only for development purposes! It is HIGHLY recommended to add `.gitignore`, and include your .env file there if you will be pushing your project to GitHub. + +Now, import your private key from the environment variables and create your Account object. +```js +const accountAddress = // 'PASTE_ACCOUNT_PUBLIC_ADDRESS_HERE'; +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:account}} +``` + +Now, let's create a Contract object in order to interact with our contract. In order to create the Contract object, we need the ABI and the address of our contract. The ABI contains information about what kind of data structures and functions there are in our contract so that we can interact with them using SDKs like Starknet-js. + +We will copy `./target/simple_storage_SimpleStorage.contract_class.json` to `abi.json` in the Scarb project folder. The beginning of the content of the ABI file should look like this: + +```json +{"sierra_program":["0x1","0x5","0x0","0x2","0x6","0x3","0x98","0x68","0x18", //... +``` + +We can then create the Account object and the Contract object in our `index.js` file: + +```js +const contractAddress = 'PASTE_CONTRACT_ADDRESS_HERE'; +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:contract}} +``` + +The setup is finished! By calling the `fn get(self: @ContractState) -> u128` function, we will be able to read the `stored_data` variable from the contract: + +```js +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:get}} +``` + +In order to run your code, run the command `node index.js` in your project directory. After a short amount of time, you should see a "0" as the stored data. + +Now, we will set a new number to the `stored_data` variable by calling the `fn set(self: @mut ContractState, new_data: u128)` function. This is an `INVOKE` transaction, so we need to sign the transaction with our account's private key and pass along the calldata. + +The transaction is signed and broadcasted to the network and it can takes a few seconds for the transaction to be confirmed. + +```js +{{#include ../../listings/applications/simple_storage_starknetjs/index.js:set}} +``` diff --git a/src/getting-started/interacting/how_to_deploy.md b/src/getting-started/interacting/how_to_deploy.md new file mode 100644 index 00000000..232d97a0 --- /dev/null +++ b/src/getting-started/interacting/how_to_deploy.md @@ -0,0 +1,79 @@ +## Declaring and Deploying Your Contract + +We will use Starkli to declare and deploy a smart contract on Starknet. Make sure that [Starkli](https://github.com/xJonathanLEI/starkli) is installed on your device. You can check out the [starkli book](https://book.starkli.rs/) for more information. + +We will need an account, so first we will create one. If you already have one, you can skip this step and move directly to the part where we declare our contract. + +### Creating a new account: + +You should move to the directory where you want to access your account keystores, and then create a new folder for the wallet. + +```console +$ mkdir ./starkli-wallet +``` + +Create a new signer. You will be instructed to enter a password to encrypt your private key: + +```console +$ starkli signer keystore new ./starkli-wallet/keystore.json +``` + +After this command, the path of the encrypted keystore file is shown which will be needed during the declaration and deployment of the contract. + +Export the keystore path in order not to call `--keystore` in every command: + +```console +$ export STARKNET_KEYSTORE="./starkli-wallet/keystore.json" +``` + +Initialize the account with the following command using OpenZeppelin's class deployed on Starknet. + +```console +$ starkli account oz init ./starkli-wallet/account.json +``` + +After this command, the address of the account is shown once it is deployed along with the deploy command. Deploy the account: + +```console +$ starkli account deploy ./starkli-wallet/account.json +``` + +This command wants you to fund the address (given in the instructions below the command) in order to deploy the account on the Starknet Sepolia Testnet. We need Starknet Sepolia testnet ethers which could be obtained from [this faucet](https://starknet-faucet.vercel.app/). + +Once the transaction is confirmed on the faucet page, press ENTER, and the account will be deployed on Starknet Sepolia! Try to find your account on [Voyager Sepolia](https://sepolia.voyager.online/)! + +### Declaring & Deploying your Contract: + +Firstly, you need to declare your contract which will create a class on Starknet Sepolia. Note that we will use the Sierra program in `./target/ProjectName_ContractName.contract_class.json` in your Scarb folder. + +> If you are deploying a contract code that is already used, you can skip the declaration step because the class hash is already declared on the network. One example of this is when you are deploying common contract instances such as ERC20 or ERC721 contracts. + +**Note:** The command below is written to run in the directory of the Scarb folder. + +```console +$ starkli declare \ + --keystore /path/to/starkli-wallet/keystore.json \ + --account /path/to/starkli-wallet/account.json \ + --watch ./target/dev/simple_storage_SimpleStorage.contract_class.json +``` + +After this command, the class hash for your contract is declared. You should be able to find the hash under the command: + +```console +Class hash declared: +0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +``` + +Check the [Voyager Class Page](https://sepolia.voyager.online/class/0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4). +Now, it's time to deploy the contract. Add the clash hash given above after `--watch`: + +```console +$ starkli deploy \ + --keystore /path/to/starkli-wallet/keystore.json \ + --account /path/to/starkli-wallet/account.json \ + --watch 0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4 +``` + +You should now see the address of the deployed contract. Congratulations, you have deployed your contract on Starknet Sepolia Testnet! +Check the [Voyager Contract Page](https://sepolia.voyager.online/contract/0x01bb7d67375782ab08178b444dbda2b0c1c9ff4469c421124f54e1d8257f2e97) to see your contract! +Additionally, you can also find all contract instances of a given class on the Voyager Class Page as well, for example, [this page](https://sepolia.voyager.online/class/0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4#contracts4). From 1798154aacdfc7e84579674ecea2fe0c58a57966 Mon Sep 17 00:00:00 2001 From: Jules Doumeche <30329843+julio4@users.noreply.github.com> Date: Tue, 25 Jun 2024 16:58:31 +0900 Subject: [PATCH 20/36] chore: scarb, foundry, oz updates (#227) --- .tool-versions | 4 +-- Scarb.lock | 8 +++--- Scarb.toml | 6 ++--- .../constant_product_amm/src/tests.cairo | 11 +++----- .../staking/src/tests/staking_tests.cairo | 13 +++++++--- .../staking/src/tests/tokens.cairo | 26 ++++++++++++++----- .../applications/timelock/src/erc721.cairo | 6 ++--- 7 files changed, 45 insertions(+), 29 deletions(-) diff --git a/.tool-versions b/.tool-versions index c62bafce..dc8930c0 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.6.4 -starknet-foundry 0.24.0 +scarb 2.6.5 +starknet-foundry 0.25.0 diff --git a/Scarb.lock b/Scarb.lock index dcae197b..c5dc5433 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -98,8 +98,8 @@ dependencies = [ [[package]] name = "openzeppelin" -version = "0.11.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.11.0#a83f36b23f1af6e160288962be4a2701c3ecbcda" +version = "0.14.0" +source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.14.0#f091c4f51ddeb10297db984acae965328c5a4e5b" [[package]] name = "scarb" @@ -115,8 +115,8 @@ version = "0.1.0" [[package]] name = "snforge_std" -version = "0.24.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.24.0#95e9fb09cb91b3c05295915179ee1b55bf923653" +version = "0.25.0" +source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.25.0#5b366e24821e530fea97f11b211d220e8493fbea" [[package]] name = "staking" diff --git a/Scarb.toml b/Scarb.toml index 4c5c042d..d15c540a 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -12,10 +12,10 @@ test = "$(git rev-parse --show-toplevel)/scripts/test_resolver.sh" [workspace.tool.snforge] [workspace.dependencies] -starknet = ">=2.6.3" -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.11.0" } +starknet = ">=2.6.4" +openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.14.0" } components = { path = "listings/applications/components" } -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.24.0" } +snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.25.0" } [workspace.package] description = "Collection of examples of how to use the Cairo programming language to create smart contracts on Starknet." diff --git a/listings/applications/constant_product_amm/src/tests.cairo b/listings/applications/constant_product_amm/src/tests.cairo index 412d9ce9..a94d111c 100644 --- a/listings/applications/constant_product_amm/src/tests.cairo +++ b/listings/applications/constant_product_amm/src/tests.cairo @@ -1,16 +1,13 @@ #[starknet::contract] pub mod ERC20Token { - use openzeppelin::token::erc20::ERC20Component; + use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC20Component, storage: erc20, event: ERC20Event); + // ERC20 Mixin #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; - #[abi(embed_v0)] - impl ERC20MetadataImpl = ERC20Component::ERC20MetadataImpl; - #[abi(embed_v0)] - impl ERC20CamelOnlyImpl = ERC20Component::ERC20CamelOnlyImpl; + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; #[storage] @@ -35,7 +32,7 @@ pub mod ERC20Token { symbol: ByteArray ) { self.erc20.initializer(name, symbol); - self.erc20._mint(recipient, initial_supply); + self.erc20.mint(recipient, initial_supply); } } // Wait for OZ #953 fix diff --git a/listings/applications/staking/src/tests/staking_tests.cairo b/listings/applications/staking/src/tests/staking_tests.cairo index d1004867..6ff1e1c8 100644 --- a/listings/applications/staking/src/tests/staking_tests.cairo +++ b/listings/applications/staking/src/tests/staking_tests.cairo @@ -41,9 +41,14 @@ mod tests { fn deploy_erc20( class_hash: felt252, name: ByteArray, symbol: ByteArray ) -> (ContractAddress, IERC20Dispatcher) { + let supply: u256 = 1000000; + let recipient = contract_address_const::<'recipient'>(); + let mut call_data: Array = ArrayTrait::new(); Serde::serialize(@name, ref call_data); Serde::serialize(@symbol, ref call_data); + Serde::serialize(@supply, ref call_data); + Serde::serialize(@recipient, ref call_data); let address = deploy_util(class_hash, call_data); (address, IERC20Dispatcher { contract_address: address }) @@ -65,10 +70,10 @@ mod tests { fn setup() -> Deployment { let (staking_token_address, staking_token) = deploy_erc20( - StakingToken::TEST_CLASS_HASH, "StakingToken", "StakingTKN" + StakingToken::TEST_CLASS_HASH, "StakingToken", "StakingTKN", ); let (reward_token_address, reward_token) = deploy_erc20( - RewardToken::TEST_CLASS_HASH, "RewardToken", "RewardTKN" + RewardToken::TEST_CLASS_HASH, "RewardToken", "RewardTKN", ); let (_, staking_contract) = deploy_staking_contract( @@ -85,7 +90,7 @@ mod tests { let mut state = StakingToken::contract_state_for_testing(); // pretend as if we were in the deployed staking token contract set_contract_address(deploy.staking_token.contract_address); - state.erc20._mint(recipient, amount); + state.erc20.mint(recipient, amount); // approve staking contract to spend user's tokens set_contract_address(recipient); @@ -99,7 +104,7 @@ mod tests { let mut state = RewardToken::contract_state_for_testing(); // pretend as if we were in the deployed reward token contract set_contract_address(reward_token_address); - state.erc20._mint(deployed_contract, amount); + state.erc20.mint(deployed_contract, amount); } #[test] diff --git a/listings/applications/staking/src/tests/tokens.cairo b/listings/applications/staking/src/tests/tokens.cairo index c6ce3a19..ea788565 100644 --- a/listings/applications/staking/src/tests/tokens.cairo +++ b/listings/applications/staking/src/tests/tokens.cairo @@ -1,12 +1,12 @@ #[starknet::contract] pub mod RewardToken { - use openzeppelin::token::erc20::ERC20Component; + use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC20Component, storage: erc20, event: ERC20Event); #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; #[storage] @@ -23,20 +23,27 @@ pub mod RewardToken { } #[constructor] - fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) { + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress + ) { self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); } } #[starknet::contract] pub mod StakingToken { - use openzeppelin::token::erc20::ERC20Component; + use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl}; use starknet::ContractAddress; component!(path: ERC20Component, storage: erc20, event: ERC20Event); #[abi(embed_v0)] - impl ERC20Impl = ERC20Component::ERC20Impl; + impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl; impl ERC20InternalImpl = ERC20Component::InternalImpl; #[storage] @@ -53,7 +60,14 @@ pub mod StakingToken { } #[constructor] - fn constructor(ref self: ContractState, name: ByteArray, symbol: ByteArray) { + fn constructor( + ref self: ContractState, + name: ByteArray, + symbol: ByteArray, + initial_supply: u256, + recipient: ContractAddress + ) { self.erc20.initializer(name, symbol); + self.erc20.mint(recipient, initial_supply); } } diff --git a/listings/applications/timelock/src/erc721.cairo b/listings/applications/timelock/src/erc721.cairo index be7a023c..fddeda94 100644 --- a/listings/applications/timelock/src/erc721.cairo +++ b/listings/applications/timelock/src/erc721.cairo @@ -2,10 +2,10 @@ pub mod ERC721 { use starknet::ContractAddress; use openzeppelin::introspection::src5::SRC5Component; - use openzeppelin::token::erc721::ERC721Component; + use openzeppelin::token::erc721::{ERC721Component, ERC721HooksEmptyImpl}; - component!(path: SRC5Component, storage: src5, event: SRC5Event); component!(path: ERC721Component, storage: erc721, event: ERC721Event); + component!(path: SRC5Component, storage: src5, event: SRC5Event); // ERC20Mixin #[abi(embed_v0)] @@ -39,6 +39,6 @@ pub mod ERC721 { token_id: u256 ) { self.erc721.initializer(name, symbol, base_uri); - self.erc721._mint(recipient, token_id); + self.erc721.mint(recipient, token_id); } } From 52fbc5ce262cb81746b2a9dcf86d90bdab305da5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Misi=C4=87?= Date: Thu, 27 Jun 2024 04:09:58 +0200 Subject: [PATCH 21/36] feat: Advanced factory contract (#219) * add initial factory * add ownable component * add caller to CounterCreated event * turn counter into campaign * fix Campaign interfaced funcs + implement donate * add _assert_is_ended + update error messages * _assert_active->_assert_campaign_active * _assert_is_ended->_assert_campaign_ended * implement withdraw * add missing assert success in donate * add title & description * update comment * implement upgrade * clean up internal funcs and imports * move hardcoded errors in Errors mod * donate -> contribute + event rename * withdraw -> claim * add store impl for contract addr. array * remove store impl * add dynamic array impl * remove dyn. array * remove descr + convert title to felt + convert target to u128 * implement updating class hashes * Make title ByteArray again + target into u256 + update ctor arg serialization * refactor serialization + add back description * remove unused contracts * add 1 test * add get_description * add correct deps * add alexandria to toml * format factory.cairo * add missing snforge workspace * add missing getters + tests * add factory deploy tests * add class hash update test + event assertions * assert old class hash prior to update * remove commented out test * use common alex. storage workspace in using_lists * add missing newline in toml * move factory tests to separate file * add scaffold docs for contracts * add end_time asserts * refactor private asserts * check if target reached before claiming * add ability to withdraw funds * make contributions into a component (now iterable) * refactor 'withhold' - contrs map to amt_idx * add get_contributors func * get_contributors -> get_contributions * total_contributors->contributor_count * add tests for campaign upgrade and deploy + update all relevant code in factory * add status to campaign * add close fn * pass desired donation token in ctor * merge all getters into get_details * return total_contributions in details * remove rev version from alexandria dep * verbose names * reorg. folder structure * add tag to alexandria dep * campaign_upgrade.cairo->mock_upgrade.cairo * add explicit alexandria rev + make crowdfunding contracts standalone chapters * add status pending * field rename: factory->creator * refund users when upgrading campaign * Make owner the calling address, and creator is the campaign manager * add get_contributor (amount) func * Add successful campaign test * update comment for upgrade * _refund_all->_withdraw_all * update checks for withdraw * rework contribute * rework all funcs * unsuccessful -> failed * calc end_time in start fn * calc end_time in upgrade fn * makes upgrades callable only by creators in factory * fix factory tests * fix crowdfunding tests * reduce total contri. when withdraw from act. camp * add refund fn * refactor withdraw_all to use _refund * pending->draft * fix mock and tests * add test for close * add test for withdraw * upgrade > update end_time only if duration provided * close->cancel * rename to more align with Solidity by example * target->goal * remove comment * err CLOSED->CANCELED + check active in unpledge * contributor->pledger * add campaign doc content * remove draft status * add start_time * remove Status * update doc for campaign * move total_pledges to pledgeable * reorder alphabetically * remove Launched event + upgrade mock * TARGET->GOAL * reorder params in Details * add inline to _refund * add new pledgeable tests * add getX tests + add get_pledge_count * refactor pledger_to_amount_index->pledger_to_amount * Add tests with 1000 pledgers * add test for add + update existing pledger * reenable lib * Add link to adv. factory in crowdfunding point 9 * write the adv. factory chapter * upgrade_campaign_implementation-> upgrade_campaign + comment updates * rename get_pledgers_as_arr->array * Use ERC20Upgradeable instead of ERC20 preset * Add missing token recipient ctor argument in crowdfunding tests --------- Co-authored-by: Nenad --- Scarb.lock | 19 + Scarb.toml | 2 + .../advanced-concepts/using_lists/Scarb.toml | 2 +- .../applications/advanced_factory/.gitignore | 2 + .../applications/advanced_factory/Scarb.toml | 18 + .../advanced_factory/src/contract.cairo | 152 +++++ .../advanced_factory/src/lib.cairo | 5 + .../advanced_factory/src/mock_upgrade.cairo | 8 + .../advanced_factory/src/tests.cairo | 194 ++++++ listings/applications/crowdfunding/.gitignore | 2 + listings/applications/crowdfunding/Scarb.toml | 19 + .../crowdfunding/src/campaign.cairo | 352 +++++++++++ .../src/campaign/pledgeable.cairo | 556 ++++++++++++++++++ .../applications/crowdfunding/src/lib.cairo | 5 + .../crowdfunding/src/mock_upgrade.cairo | 289 +++++++++ .../applications/crowdfunding/src/tests.cairo | 480 +++++++++++++++ src/SUMMARY.md | 2 + src/applications/advanced_factory.md | 13 + src/applications/crowdfunding.md | 26 + 19 files changed, 2145 insertions(+), 1 deletion(-) create mode 100644 listings/applications/advanced_factory/.gitignore create mode 100644 listings/applications/advanced_factory/Scarb.toml create mode 100644 listings/applications/advanced_factory/src/contract.cairo create mode 100644 listings/applications/advanced_factory/src/lib.cairo create mode 100644 listings/applications/advanced_factory/src/mock_upgrade.cairo create mode 100644 listings/applications/advanced_factory/src/tests.cairo create mode 100644 listings/applications/crowdfunding/.gitignore create mode 100644 listings/applications/crowdfunding/Scarb.toml create mode 100644 listings/applications/crowdfunding/src/campaign.cairo create mode 100644 listings/applications/crowdfunding/src/campaign/pledgeable.cairo create mode 100644 listings/applications/crowdfunding/src/lib.cairo create mode 100644 listings/applications/crowdfunding/src/mock_upgrade.cairo create mode 100644 listings/applications/crowdfunding/src/tests.cairo create mode 100644 src/applications/advanced_factory.md create mode 100644 src/applications/crowdfunding.md diff --git a/Scarb.lock b/Scarb.lock index c5dc5433..a055e8a9 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -1,6 +1,16 @@ # Code generated by scarb DO NOT EDIT. version = 1 +[[package]] +name = "advanced_factory" +version = "0.1.0" +dependencies = [ + "alexandria_storage", + "components", + "crowdfunding", + "snforge_std", +] + [[package]] name = "alexandria_storage" version = "0.3.0" @@ -44,6 +54,15 @@ version = "0.1.0" name = "counter" version = "0.1.0" +[[package]] +name = "crowdfunding" +version = "0.1.0" +dependencies = [ + "components", + "openzeppelin", + "snforge_std", +] + [[package]] name = "custom_type_serde" version = "0.1.0" diff --git a/Scarb.toml b/Scarb.toml index d15c540a..8df8fcb2 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -16,6 +16,8 @@ starknet = ">=2.6.4" openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.14.0" } components = { path = "listings/applications/components" } snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.25.0" } +# The latest Alexandria release supports only Cairo v2.6.0, so using explicit rev that supports Cairo v2.6.3 +alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev="800f5ad" } [workspace.package] description = "Collection of examples of how to use the Cairo programming language to create smart contracts on Starknet." diff --git a/listings/advanced-concepts/using_lists/Scarb.toml b/listings/advanced-concepts/using_lists/Scarb.toml index 3ccb4af4..20fc9020 100644 --- a/listings/advanced-concepts/using_lists/Scarb.toml +++ b/listings/advanced-concepts/using_lists/Scarb.toml @@ -5,7 +5,7 @@ edition = '2023_11' [dependencies] starknet.workspace = true -alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev="800f5ad"} +alexandria_storage.workspace = true [scripts] test.workspace = true diff --git a/listings/applications/advanced_factory/.gitignore b/listings/applications/advanced_factory/.gitignore new file mode 100644 index 00000000..73aa31e6 --- /dev/null +++ b/listings/applications/advanced_factory/.gitignore @@ -0,0 +1,2 @@ +target +.snfoundry_cache/ diff --git a/listings/applications/advanced_factory/Scarb.toml b/listings/applications/advanced_factory/Scarb.toml new file mode 100644 index 00000000..5935e01f --- /dev/null +++ b/listings/applications/advanced_factory/Scarb.toml @@ -0,0 +1,18 @@ +[package] +name = "advanced_factory" +version.workspace = true +edition = "2023_11" + +[dependencies] +starknet.workspace = true +components.workspace = true +alexandria_storage.workspace = true +snforge_std.workspace = true +crowdfunding = { path = "../crowdfunding" } + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +casm = true +build-external-contracts = ["crowdfunding::campaign::Campaign"] diff --git a/listings/applications/advanced_factory/src/contract.cairo b/listings/applications/advanced_factory/src/contract.cairo new file mode 100644 index 00000000..c07000f5 --- /dev/null +++ b/listings/applications/advanced_factory/src/contract.cairo @@ -0,0 +1,152 @@ +// ANCHOR: contract +pub use starknet::{ContractAddress, ClassHash}; + +#[starknet::interface] +pub trait ICampaignFactory { + fn create_campaign( + ref self: TContractState, + title: ByteArray, + description: ByteArray, + goal: u256, + start_time: u64, + end_time: u64, + token_address: ContractAddress + ) -> ContractAddress; + fn get_campaign_class_hash(self: @TContractState) -> ClassHash; + fn update_campaign_class_hash(ref self: TContractState, new_class_hash: ClassHash); + fn upgrade_campaign( + ref self: TContractState, campaign_address: ContractAddress, new_end_time: Option + ); +} + +#[starknet::contract] +pub mod CampaignFactory { + use core::num::traits::zero::Zero; + use starknet::{ + ContractAddress, ClassHash, SyscallResultTrait, syscalls::deploy_syscall, + get_caller_address, get_contract_address + }; + use alexandria_storage::list::{List, ListTrait}; + use crowdfunding::campaign::{ICampaignDispatcher, ICampaignDispatcherTrait}; + use components::ownable::ownable_component; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: ownable_component::Storage, + /// Store all of the created campaign instances' addresses and thei class hashes + campaigns: LegacyMap<(ContractAddress, ContractAddress), ClassHash>, + /// Store the class hash of the contract to deploy + campaign_class_hash: ClassHash, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + #[flat] + OwnableEvent: ownable_component::Event, + CampaignClassHashUpgraded: CampaignClassHashUpgraded, + CampaignCreated: CampaignCreated, + ClassHashUpdated: ClassHashUpdated, + } + + #[derive(Drop, starknet::Event)] + pub struct ClassHashUpdated { + pub new_class_hash: ClassHash, + } + + #[derive(Drop, starknet::Event)] + pub struct CampaignClassHashUpgraded { + pub campaign: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct CampaignCreated { + pub creator: ContractAddress, + pub contract_address: ContractAddress + } + + pub mod Errors { + pub const CLASS_HASH_ZERO: felt252 = 'Class hash cannot be zero'; + pub const ZERO_ADDRESS: felt252 = 'Zero address'; + pub const SAME_IMPLEMENTATION: felt252 = 'Implementation is unchanged'; + pub const CAMPAIGN_NOT_FOUND: felt252 = 'Campaign not found'; + } + + #[constructor] + fn constructor(ref self: ContractState, class_hash: ClassHash) { + assert(class_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); + self.campaign_class_hash.write(class_hash); + self.ownable._init(get_caller_address()); + } + + + #[abi(embed_v0)] + impl CampaignFactory of super::ICampaignFactory { + fn create_campaign( + ref self: ContractState, + title: ByteArray, + description: ByteArray, + goal: u256, + start_time: u64, + end_time: u64, + token_address: ContractAddress, + ) -> ContractAddress { + let creator = get_caller_address(); + + // Create contructor arguments + let mut constructor_calldata: Array:: = array![]; + ((creator, title, description, goal), start_time, end_time, token_address) + .serialize(ref constructor_calldata); + + // Contract deployment + let (contract_address, _) = deploy_syscall( + self.campaign_class_hash.read(), 0, constructor_calldata.span(), false + ) + .unwrap_syscall(); + + // track new campaign instance + self.campaigns.write((creator, contract_address), self.campaign_class_hash.read()); + + self.emit(Event::CampaignCreated(CampaignCreated { creator, contract_address })); + + contract_address + } + + fn get_campaign_class_hash(self: @ContractState) -> ClassHash { + self.campaign_class_hash.read() + } + + fn update_campaign_class_hash(ref self: ContractState, new_class_hash: ClassHash) { + self.ownable._assert_only_owner(); + assert(new_class_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); + + self.campaign_class_hash.write(new_class_hash); + + self.emit(Event::ClassHashUpdated(ClassHashUpdated { new_class_hash })); + } + + fn upgrade_campaign( + ref self: ContractState, campaign_address: ContractAddress, new_end_time: Option + ) { + assert(campaign_address.is_non_zero(), Errors::ZERO_ADDRESS); + + let creator = get_caller_address(); + let old_class_hash = self.campaigns.read((creator, campaign_address)); + assert(old_class_hash.is_non_zero(), Errors::CAMPAIGN_NOT_FOUND); + assert(old_class_hash != self.campaign_class_hash.read(), Errors::SAME_IMPLEMENTATION); + + let campaign = ICampaignDispatcher { contract_address: campaign_address }; + campaign.upgrade(self.campaign_class_hash.read(), new_end_time); + } + } +} +// ANCHOR_END: contract + + diff --git a/listings/applications/advanced_factory/src/lib.cairo b/listings/applications/advanced_factory/src/lib.cairo new file mode 100644 index 00000000..541355ed --- /dev/null +++ b/listings/applications/advanced_factory/src/lib.cairo @@ -0,0 +1,5 @@ +mod contract; +mod mock_upgrade; + +#[cfg(test)] +mod tests; diff --git a/listings/applications/advanced_factory/src/mock_upgrade.cairo b/listings/applications/advanced_factory/src/mock_upgrade.cairo new file mode 100644 index 00000000..919c40ae --- /dev/null +++ b/listings/applications/advanced_factory/src/mock_upgrade.cairo @@ -0,0 +1,8 @@ +#[starknet::contract] +pub mod MockContract { + #[storage] + struct Storage {} + #[event] + #[derive(Drop, starknet::Event)] + enum Event {} +} diff --git a/listings/applications/advanced_factory/src/tests.cairo b/listings/applications/advanced_factory/src/tests.cairo new file mode 100644 index 00000000..eb0cc1b5 --- /dev/null +++ b/listings/applications/advanced_factory/src/tests.cairo @@ -0,0 +1,194 @@ +use core::traits::TryInto; +use core::clone::Clone; +use core::result::ResultTrait; +use advanced_factory::contract::{ + CampaignFactory, ICampaignFactoryDispatcher, ICampaignFactoryDispatcherTrait +}; +use starknet::{ + ContractAddress, ClassHash, get_block_timestamp, contract_address_const, get_caller_address +}; +use snforge_std::{ + declare, ContractClass, ContractClassTrait, start_cheat_caller_address, + stop_cheat_caller_address, spy_events, SpyOn, EventSpy, EventAssertions, get_class_hash +}; + +// Define a goal contract to deploy +use crowdfunding::campaign::{Campaign, ICampaignDispatcher, ICampaignDispatcherTrait}; +use components::ownable::{IOwnableDispatcher, IOwnableDispatcherTrait}; + + +/// Deploy a campaign factory contract with the provided campaign class hash +fn deploy_factory_with(campaign_class_hash: ClassHash) -> ICampaignFactoryDispatcher { + let mut constructor_calldata: @Array:: = @array![campaign_class_hash.into()]; + + let contract = declare("CampaignFactory").unwrap(); + let contract_address = contract.precalculate_address(constructor_calldata); + let factory_owner: ContractAddress = contract_address_const::<'factory_owner'>(); + start_cheat_caller_address(contract_address, factory_owner); + + contract.deploy(constructor_calldata).unwrap(); + + stop_cheat_caller_address(contract_address); + + ICampaignFactoryDispatcher { contract_address } +} + +/// Deploy a campaign factory contract with default campaign class hash +fn deploy_factory() -> ICampaignFactoryDispatcher { + let campaign_class_hash = declare("Campaign").unwrap().class_hash; + deploy_factory_with(campaign_class_hash) +} + +#[test] +fn test_deploy_factory() { + let campaign_class_hash = declare("Campaign").unwrap().class_hash; + let factory = deploy_factory_with(campaign_class_hash); + + assert_eq!(factory.get_campaign_class_hash(), campaign_class_hash); + + let factory_owner: ContractAddress = contract_address_const::<'factory_owner'>(); + let factory_ownable = IOwnableDispatcher { contract_address: factory.contract_address }; + assert_eq!(factory_ownable.owner(), factory_owner); +} + +#[test] +fn test_create_campaign() { + let factory = deploy_factory(); + + let mut spy = spy_events(SpyOn::One(factory.contract_address)); + + let campaign_creator: ContractAddress = contract_address_const::<'campaign_creator'>(); + start_cheat_caller_address(factory.contract_address, campaign_creator); + + let title: ByteArray = "New campaign"; + let description: ByteArray = "Some description"; + let goal: u256 = 10000; + let start_time = get_block_timestamp(); + let end_time = start_time + 60; + let token = contract_address_const::<'token'>(); + + let campaign_address = factory + .create_campaign(title.clone(), description.clone(), goal, start_time, end_time, token); + let campaign = ICampaignDispatcher { contract_address: campaign_address }; + + let details = campaign.get_details(); + assert_eq!(details.title, title); + assert_eq!(details.description, description); + assert_eq!(details.goal, goal); + assert_eq!(details.start_time, start_time); + assert_eq!(details.end_time, end_time); + assert_eq!(details.claimed, false); + assert_eq!(details.canceled, false); + assert_eq!(details.token, token); + assert_eq!(details.total_pledges, 0); + assert_eq!(details.creator, campaign_creator); + + let campaign_ownable = IOwnableDispatcher { contract_address: campaign_address }; + assert_eq!(campaign_ownable.owner(), factory.contract_address); + + spy + .assert_emitted( + @array![ + ( + factory.contract_address, + CampaignFactory::Event::CampaignCreated( + CampaignFactory::CampaignCreated { + creator: campaign_creator, contract_address: campaign_address + } + ) + ) + ] + ); +} + +#[test] +fn test_uprade_campaign_class_hash() { + let factory = deploy_factory(); + let old_class_hash = factory.get_campaign_class_hash(); + let new_class_hash = declare("MockContract").unwrap().class_hash; + + let token = contract_address_const::<'token'>(); + + // deploy a pending campaign with the old class hash + let start_time_pending = get_block_timestamp() + 20; + let end_time_pending = start_time_pending + 60; + let pending_campaign_creator = contract_address_const::<'pending_campaign_creator'>(); + start_cheat_caller_address(factory.contract_address, pending_campaign_creator); + let pending_campaign = factory + .create_campaign( + "title 1", "description 1", 10000, start_time_pending, end_time_pending, token + ); + + assert_eq!(old_class_hash, get_class_hash(pending_campaign)); + + // deploy an active campaign with the old class hash + let start_time_active = get_block_timestamp(); + let end_time_active = start_time_active + 60; + let active_campaign_creator = contract_address_const::<'active_campaign_creator'>(); + start_cheat_caller_address(factory.contract_address, active_campaign_creator); + let active_campaign = factory + .create_campaign( + "title 2", "description 2", 20000, start_time_active, end_time_active, token + ); + + assert_eq!(old_class_hash, get_class_hash(active_campaign)); + + // update the factory's campaign class hash value + let mut spy = spy_events( + SpyOn::Multiple(array![factory.contract_address, pending_campaign, active_campaign]) + ); + + let factory_owner = contract_address_const::<'factory_owner'>(); + start_cheat_caller_address(factory.contract_address, factory_owner); + factory.update_campaign_class_hash(new_class_hash); + + assert_eq!(factory.get_campaign_class_hash(), new_class_hash); + assert_eq!(old_class_hash, get_class_hash(pending_campaign)); + assert_eq!(old_class_hash, get_class_hash(active_campaign)); + + spy + .assert_emitted( + @array![ + ( + factory.contract_address, + CampaignFactory::Event::ClassHashUpdated( + CampaignFactory::ClassHashUpdated { new_class_hash } + ) + ) + ] + ); + + // upgrade pending campaign + start_cheat_caller_address(factory.contract_address, pending_campaign_creator); + factory.upgrade_campaign(pending_campaign, Option::None); + + assert_eq!(get_class_hash(pending_campaign), new_class_hash); + assert_eq!(get_class_hash(active_campaign), old_class_hash); + + spy + .assert_emitted( + @array![ + ( + pending_campaign, + Campaign::Event::Upgraded(Campaign::Upgraded { implementation: new_class_hash }) + ) + ] + ); + + // upgrade active campaign + start_cheat_caller_address(factory.contract_address, active_campaign_creator); + factory.upgrade_campaign(active_campaign, Option::None); + + assert_eq!(get_class_hash(pending_campaign), new_class_hash); + assert_eq!(get_class_hash(active_campaign), new_class_hash); + + spy + .assert_emitted( + @array![ + ( + active_campaign, + Campaign::Event::Upgraded(Campaign::Upgraded { implementation: new_class_hash }) + ) + ] + ); +} diff --git a/listings/applications/crowdfunding/.gitignore b/listings/applications/crowdfunding/.gitignore new file mode 100644 index 00000000..73aa31e6 --- /dev/null +++ b/listings/applications/crowdfunding/.gitignore @@ -0,0 +1,2 @@ +target +.snfoundry_cache/ diff --git a/listings/applications/crowdfunding/Scarb.toml b/listings/applications/crowdfunding/Scarb.toml new file mode 100644 index 00000000..074a2713 --- /dev/null +++ b/listings/applications/crowdfunding/Scarb.toml @@ -0,0 +1,19 @@ +[package] +name = "crowdfunding" +version.workspace = true +edition = "2023_11" + +[lib] + +[dependencies] +starknet.workspace = true +openzeppelin.workspace = true +components.workspace = true +snforge_std.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +casm = true +build-external-contracts = ["openzeppelin::presets::erc20::ERC20Upgradeable"] diff --git a/listings/applications/crowdfunding/src/campaign.cairo b/listings/applications/crowdfunding/src/campaign.cairo new file mode 100644 index 00000000..e5b5faf6 --- /dev/null +++ b/listings/applications/crowdfunding/src/campaign.cairo @@ -0,0 +1,352 @@ +pub mod pledgeable; + +// ANCHOR: contract +use starknet::{ClassHash, ContractAddress}; + +#[derive(Drop, Serde)] +pub struct Details { + pub canceled: bool, + pub claimed: bool, + pub creator: ContractAddress, + pub description: ByteArray, + pub end_time: u64, + pub goal: u256, + pub start_time: u64, + pub title: ByteArray, + pub token: ContractAddress, + pub total_pledges: u256, +} + +#[starknet::interface] +pub trait ICampaign { + fn claim(ref self: TContractState); + fn cancel(ref self: TContractState, reason: ByteArray); + fn pledge(ref self: TContractState, amount: u256); + fn get_pledge(self: @TContractState, pledger: ContractAddress) -> u256; + fn get_pledgers(self: @TContractState) -> Array; + fn get_details(self: @TContractState) -> Details; + fn refund(ref self: TContractState, pledger: ContractAddress, reason: ByteArray); + fn upgrade(ref self: TContractState, impl_hash: ClassHash, new_end_time: Option); + fn unpledge(ref self: TContractState, reason: ByteArray); +} + +#[starknet::contract] +pub mod Campaign { + use components::ownable::ownable_component::OwnableInternalTrait; + use core::num::traits::zero::Zero; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use starknet::{ + ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, contract_address_const, + get_caller_address, get_contract_address, class_hash::class_hash_const + }; + use components::ownable::ownable_component; + use super::pledgeable::pledgeable_component; + use super::Details; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + component!(path: pledgeable_component, storage: pledges, event: PledgeableEvent); + + #[abi(embed_v0)] + pub impl OwnableImpl = ownable_component::Ownable; + impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; + #[abi(embed_v0)] + impl PledgeableImpl = pledgeable_component::Pledgeable; + + #[storage] + struct Storage { + canceled: bool, + claimed: bool, + creator: ContractAddress, + description: ByteArray, + end_time: u64, + goal: u256, + #[substorage(v0)] + ownable: ownable_component::Storage, + #[substorage(v0)] + pledges: pledgeable_component::Storage, + start_time: u64, + title: ByteArray, + token: IERC20Dispatcher, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + Claimed: Claimed, + Canceled: Canceled, + #[flat] + OwnableEvent: ownable_component::Event, + PledgeableEvent: pledgeable_component::Event, + PledgeMade: PledgeMade, + Refunded: Refunded, + RefundedAll: RefundedAll, + Unpledged: Unpledged, + Upgraded: Upgraded, + } + + #[derive(Drop, starknet::Event)] + pub struct Canceled { + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct Claimed { + pub amount: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct PledgeMade { + #[key] + pub pledger: ContractAddress, + pub amount: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct Refunded { + #[key] + pub pledger: ContractAddress, + pub amount: u256, + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct RefundedAll { + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct Unpledged { + #[key] + pub pledger: ContractAddress, + pub amount: u256, + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct Upgraded { + pub implementation: ClassHash + } + + pub mod Errors { + pub const CANCELED: felt252 = 'Campaign canceled'; + pub const CLAIMED: felt252 = 'Campaign already claimed'; + pub const CLASS_HASH_ZERO: felt252 = 'Class hash zero'; + pub const CREATOR_ZERO: felt252 = 'Creator address zero'; + pub const ENDED: felt252 = 'Campaign already ended'; + pub const END_BEFORE_NOW: felt252 = 'End time < now'; + pub const END_BEFORE_START: felt252 = 'End time < start time'; + pub const END_BIGGER_THAN_MAX: felt252 = 'End time > max duration'; + pub const NOTHING_TO_REFUND: felt252 = 'Nothing to refund'; + pub const NOTHING_TO_UNPLEDGE: felt252 = 'Nothing to unpledge'; + pub const NOT_CREATOR: felt252 = 'Not creator'; + pub const NOT_STARTED: felt252 = 'Campaign not started'; + pub const PLEDGES_LOCKED: felt252 = 'Goal reached, pledges locked'; + pub const START_TIME_IN_PAST: felt252 = 'Start time < now'; + pub const STILL_ACTIVE: felt252 = 'Campaign not ended'; + pub const GOAL_NOT_REACHED: felt252 = 'Goal not reached'; + pub const TITLE_EMPTY: felt252 = 'Title empty'; + pub const TRANSFER_FAILED: felt252 = 'Transfer failed'; + pub const ZERO_ADDRESS_CALLER: felt252 = 'Caller address zero'; + pub const ZERO_ADDRESS_PLEDGER: felt252 = 'Pledger address zero'; + pub const ZERO_ADDRESS_TOKEN: felt252 = 'Token address zerp'; + pub const ZERO_DONATION: felt252 = 'Donation must be > 0'; + pub const ZERO_GOAL: felt252 = 'Goal must be > 0'; + pub const ZERO_PLEDGES: felt252 = 'No pledges to claim'; + } + + const NINETY_DAYS: u64 = consteval_int!(90 * 24 * 60 * 60); + + #[constructor] + fn constructor( + ref self: ContractState, + creator: ContractAddress, + title: ByteArray, + description: ByteArray, + goal: u256, + start_time: u64, + end_time: u64, + token_address: ContractAddress, + ) { + assert(creator.is_non_zero(), Errors::CREATOR_ZERO); + assert(title.len() > 0, Errors::TITLE_EMPTY); + assert(goal > 0, Errors::ZERO_GOAL); + assert(start_time >= get_block_timestamp(), Errors::START_TIME_IN_PAST); + assert(end_time >= start_time, Errors::END_BEFORE_START); + assert(end_time <= get_block_timestamp() + NINETY_DAYS, Errors::END_BIGGER_THAN_MAX); + assert(token_address.is_non_zero(), Errors::ZERO_ADDRESS_TOKEN); + + self.creator.write(creator); + self.title.write(title); + self.goal.write(goal); + self.description.write(description); + self.start_time.write(start_time); + self.end_time.write(end_time); + self.token.write(IERC20Dispatcher { contract_address: token_address }); + self.ownable._init(get_caller_address()); + } + + #[abi(embed_v0)] + impl Campaign of super::ICampaign { + fn cancel(ref self: ContractState, reason: ByteArray) { + self._assert_only_creator(); + assert(!self.canceled.read(), Errors::CANCELED); + assert(!self.claimed.read(), Errors::CLAIMED); + + self.canceled.write(true); + + self._refund_all(reason.clone()); + + self.emit(Event::Canceled(Canceled { reason })); + } + + /// Sends the funds to the campaign creator. + /// It leaves the pledge data intact as a testament to campaign success + fn claim(ref self: ContractState) { + self._assert_only_creator(); + assert(self._is_started(), Errors::NOT_STARTED); + assert(self._is_ended(), Errors::STILL_ACTIVE); + assert(!self.claimed.read(), Errors::CLAIMED); + assert(self._is_goal_reached(), Errors::GOAL_NOT_REACHED); + // no need to check if canceled; if it was, then the goal wouldn't have been reached + + let this = get_contract_address(); + let token = self.token.read(); + let amount = token.balance_of(this); + assert(amount > 0, Errors::ZERO_PLEDGES); + + self.claimed.write(true); + + let owner = get_caller_address(); + let success = token.transfer(owner, amount); + assert(success, Errors::TRANSFER_FAILED); + + self.emit(Event::Claimed(Claimed { amount })); + } + + fn get_details(self: @ContractState) -> Details { + Details { + canceled: self.canceled.read(), + claimed: self.claimed.read(), + creator: self.creator.read(), + description: self.description.read(), + end_time: self.end_time.read(), + goal: self.goal.read(), + start_time: self.start_time.read(), + title: self.title.read(), + token: self.token.read().contract_address, + total_pledges: self.pledges.get_total(), + } + } + + fn get_pledge(self: @ContractState, pledger: ContractAddress) -> u256 { + self.pledges.get(pledger) + } + + fn get_pledgers(self: @ContractState) -> Array { + self.pledges.array() + } + + fn pledge(ref self: ContractState, amount: u256) { + assert(self._is_started(), Errors::NOT_STARTED); + assert(!self._is_ended(), Errors::ENDED); + assert(!self.canceled.read(), Errors::CANCELED); + assert(amount > 0, Errors::ZERO_DONATION); + + let pledger = get_caller_address(); + let this = get_contract_address(); + let success = self.token.read().transfer_from(pledger, this, amount); + assert(success, Errors::TRANSFER_FAILED); + + self.pledges.add(pledger, amount); + + self.emit(Event::PledgeMade(PledgeMade { pledger, amount })); + } + + fn refund(ref self: ContractState, pledger: ContractAddress, reason: ByteArray) { + self._assert_only_creator(); + assert(self._is_started(), Errors::NOT_STARTED); + assert(!self.claimed.read(), Errors::CLAIMED); + assert(!self.canceled.read(), Errors::CANCELED); + assert(pledger.is_non_zero(), Errors::ZERO_ADDRESS_PLEDGER); + assert(self.pledges.get(pledger) != 0, Errors::NOTHING_TO_REFUND); + + let amount = self._refund(pledger); + + self.emit(Event::Refunded(Refunded { pledger, amount, reason })) + } + + fn unpledge(ref self: ContractState, reason: ByteArray) { + assert(self._is_started(), Errors::NOT_STARTED); + assert(!self._is_goal_reached(), Errors::PLEDGES_LOCKED); + assert(self.pledges.get(get_caller_address()) != 0, Errors::NOTHING_TO_UNPLEDGE); + + let pledger = get_caller_address(); + let amount = self._refund(pledger); + + self.emit(Event::Unpledged(Unpledged { pledger, amount, reason })); + } + + fn upgrade(ref self: ContractState, impl_hash: ClassHash, new_end_time: Option) { + self.ownable._assert_only_owner(); + assert(impl_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); + + // only active campaigns have pledges to refund and an end time to update + if self._is_started() { + if let Option::Some(end_time) = new_end_time { + assert(end_time >= get_block_timestamp(), Errors::END_BEFORE_NOW); + assert( + end_time <= get_block_timestamp() + NINETY_DAYS, Errors::END_BIGGER_THAN_MAX + ); + self.end_time.write(end_time); + }; + self._refund_all("contract upgraded"); + } + + starknet::syscalls::replace_class_syscall(impl_hash).unwrap_syscall(); + + self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })); + } + } + + #[generate_trait] + impl CampaignInternalImpl of CampaignInternalTrait { + fn _assert_only_creator(self: @ContractState) { + let caller = get_caller_address(); + assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER); + assert(caller == self.creator.read(), Errors::NOT_CREATOR); + } + + fn _is_ended(self: @ContractState) -> bool { + get_block_timestamp() >= self.end_time.read() + } + + fn _is_goal_reached(self: @ContractState) -> bool { + self.pledges.get_total() >= self.goal.read() + } + + fn _is_started(self: @ContractState) -> bool { + get_block_timestamp() >= self.start_time.read() + } + + #[inline(always)] + fn _refund(ref self: ContractState, pledger: ContractAddress) -> u256 { + let amount = self.pledges.remove(pledger); + + let success = self.token.read().transfer(pledger, amount); + assert(success, Errors::TRANSFER_FAILED); + + amount + } + + fn _refund_all(ref self: ContractState, reason: ByteArray) { + let mut pledges = self.pledges.array(); + while let Option::Some(pledger) = pledges.pop_front() { + self._refund(pledger); + }; + self.emit(Event::RefundedAll(RefundedAll { reason })); + } + } +} +// ANCHOR_END: contract + + diff --git a/listings/applications/crowdfunding/src/campaign/pledgeable.cairo b/listings/applications/crowdfunding/src/campaign/pledgeable.cairo new file mode 100644 index 00000000..4000057a --- /dev/null +++ b/listings/applications/crowdfunding/src/campaign/pledgeable.cairo @@ -0,0 +1,556 @@ +// ANCHOR: component +use starknet::ContractAddress; + +#[starknet::interface] +pub trait IPledgeable { + fn add(ref self: TContractState, pledger: ContractAddress, amount: u256); + fn get(self: @TContractState, pledger: ContractAddress) -> u256; + fn get_pledger_count(self: @TContractState) -> u32; + fn array(self: @TContractState) -> Array; + fn get_total(self: @TContractState) -> u256; + fn remove(ref self: TContractState, pledger: ContractAddress) -> u256; +} + +#[starknet::component] +pub mod pledgeable_component { + use core::array::ArrayTrait; + use starknet::{ContractAddress}; + use core::num::traits::Zero; + + #[storage] + struct Storage { + index_to_pledger: LegacyMap, + pledger_to_amount: LegacyMap, + pledger_count: u32, + total_amount: u256, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event {} + + mod Errors { + pub const INCONSISTENT_STATE: felt252 = 'Non-indexed pledger found'; + } + + #[embeddable_as(Pledgeable)] + pub impl PledgeableImpl< + TContractState, +HasComponent + > of super::IPledgeable> { + fn add(ref self: ComponentState, pledger: ContractAddress, amount: u256) { + let old_amount: u256 = self.pledger_to_amount.read(pledger); + + if old_amount == 0 { + let index = self.pledger_count.read(); + self.index_to_pledger.write(index, pledger); + self.pledger_count.write(index + 1); + } + + self.pledger_to_amount.write(pledger, old_amount + amount); + self.total_amount.write(self.total_amount.read() + amount); + } + + fn get(self: @ComponentState, pledger: ContractAddress) -> u256 { + self.pledger_to_amount.read(pledger) + } + + fn get_pledger_count(self: @ComponentState) -> u32 { + self.pledger_count.read() + } + + fn array(self: @ComponentState) -> Array { + let mut result = array![]; + + let mut index = self.pledger_count.read(); + while index != 0 { + index -= 1; + let pledger = self.index_to_pledger.read(index); + result.append(pledger); + }; + + result + } + + fn get_total(self: @ComponentState) -> u256 { + self.total_amount.read() + } + + fn remove(ref self: ComponentState, pledger: ContractAddress) -> u256 { + let amount: u256 = self.pledger_to_amount.read(pledger); + + // check if the pledge even exists + if amount == 0 { + return 0; + } + + let last_index = self.pledger_count.read() - 1; + + // if there are other pledgers, we need to update our indices + if last_index != 0 { + let mut pledger_index = last_index; + loop { + if self.index_to_pledger.read(pledger_index) == pledger { + break; + } + // if pledger_to_amount contains a pledger, then so does index_to_pledger + // thus this will never underflow + pledger_index -= 1; + }; + + self.index_to_pledger.write(pledger_index, self.index_to_pledger.read(last_index)); + } + + // last_index == new pledger count + self.pledger_count.write(last_index); + self.pledger_to_amount.write(pledger, 0); + self.index_to_pledger.write(last_index, Zero::zero()); + + self.total_amount.write(self.total_amount.read() - amount); + + amount + } + } +} +// ANCHOR_END: component + +#[cfg(test)] +mod tests { + #[starknet::contract] + mod MockContract { + use super::super::pledgeable_component; + + component!(path: pledgeable_component, storage: pledges, event: PledgeableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + pledges: pledgeable_component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + PledgeableEvent: pledgeable_component::Event + } + + #[abi(embed_v0)] + impl Pledgeable = pledgeable_component::Pledgeable; + } + + use super::{pledgeable_component, IPledgeableDispatcher, IPledgeableDispatcherTrait}; + use super::pledgeable_component::{PledgeableImpl}; + use starknet::{ContractAddress, contract_address_const}; + use core::num::traits::Zero; + + type TestingState = pledgeable_component::ComponentState; + + // You can derive even `Default` on this type alias + impl TestingStateDefault of Default { + fn default() -> TestingState { + pledgeable_component::component_state_for_testing() + } + } + + #[test] + fn test_add() { + let mut pledgeable: TestingState = Default::default(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + + assert_eq!(pledgeable.get_pledger_count(), 0); + assert_eq!(pledgeable.get_total(), 0); + assert_eq!(pledgeable.get(pledger_1), 0); + assert_eq!(pledgeable.get(pledger_2), 0); + + // 1st pledge + pledgeable.add(pledger_1, 1000); + + assert_eq!(pledgeable.get_pledger_count(), 1); + assert_eq!(pledgeable.get_total(), 1000); + assert_eq!(pledgeable.get(pledger_1), 1000); + assert_eq!(pledgeable.get(pledger_2), 0); + + // 2nd pledge should be added onto 1st + pledgeable.add(pledger_1, 1000); + + assert_eq!(pledgeable.get_pledger_count(), 1); + assert_eq!(pledgeable.get_total(), 2000); + assert_eq!(pledgeable.get(pledger_1), 2000); + assert_eq!(pledgeable.get(pledger_2), 0); + + // different pledger stored separately + pledgeable.add(pledger_2, 500); + + assert_eq!(pledgeable.get_pledger_count(), 2); + assert_eq!(pledgeable.get_total(), 2500); + assert_eq!(pledgeable.get(pledger_1), 2000); + assert_eq!(pledgeable.get(pledger_2), 500); + } + + #[test] + fn test_add_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + // set up 1000 pledgers + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; // actual value set up in the while loop + let mut pledgers: Array::<(ContractAddress, u256)> = array![]; + + let mut i: felt252 = expected_pledger_count.into(); + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + pledgers.append((pledger, amount)); + expected_total += amount; + i -= 1; + }; + + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + assert_eq!(pledgeable.get_total(), expected_total); + + while let Option::Some((pledger, expected_amount)) = pledgers + .pop_front() { + assert_eq!(pledgeable.get(pledger), expected_amount); + } + } + + #[test] + fn test_add_update_first_of_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; + + // set up 1000 pledgers + let mut i: felt252 = expected_pledger_count.into(); + let first_pledger: ContractAddress = i.try_into().unwrap(); + let first_amount: u256 = i.into() * 100; + pledgeable.add(first_pledger, first_amount); + expected_total += first_amount; + + i -= 1; + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + expected_total += amount; + i -= 1; + }; + + // first pledger makes another pledge + pledgeable.add(first_pledger, 2000); + expected_total += 2000; + let expected_amount = first_amount + 2000; + + let amount = pledgeable.get(first_pledger); + assert_eq!(amount, expected_amount); + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + } + + #[test] + fn test_add_update_middle_of_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; + + // set up 1000 pledgers + let mut middle_pledger: ContractAddress = Zero::zero(); + let mut middle_amount = 0; + + let mut i: felt252 = 1000; + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + expected_total += amount; + + if i == 500 { + middle_pledger = pledger; + middle_amount = amount; + } + + i -= 1; + }; + + // middle pledger makes another pledge + pledgeable.add(middle_pledger, 2000); + expected_total += 2000; + let expected_amount = middle_amount + 2000; + + let amount = pledgeable.get(middle_pledger); + assert_eq!(amount, expected_amount); + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + } + + #[test] + fn test_add_update_last_of_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; + + // set up 1000 pledgers + let mut i: felt252 = 1000; + // remember last pledger, add it after while loop + let last_pledger: ContractAddress = i.try_into().unwrap(); + let last_amount = 100000; + + i -= 1; // leave place for the last pledger + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + expected_total += amount; + i -= 1; + }; + // add last pledger + pledgeable.add(last_pledger, last_amount); + expected_total += last_amount; + + // last pledger makes another pledge + pledgeable.add(last_pledger, 2000); + expected_total += 2000; + let expected_amount = last_amount + 2000; + + let amount = pledgeable.get(last_pledger); + assert_eq!(amount, expected_amount); + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + } + + #[test] + fn test_remove() { + let mut pledgeable: TestingState = Default::default(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + + pledgeable.add(pledger_1, 2000); + pledgeable.add(pledger_2, 3000); + // pledger_3 not added + + assert_eq!(pledgeable.get_pledger_count(), 2); + assert_eq!(pledgeable.get_total(), 5000); + assert_eq!(pledgeable.get(pledger_1), 2000); + assert_eq!(pledgeable.get(pledger_2), 3000); + assert_eq!(pledgeable.get(pledger_3), 0); + + let amount = pledgeable.remove(pledger_1); + + assert_eq!(amount, 2000); + assert_eq!(pledgeable.get_pledger_count(), 1); + assert_eq!(pledgeable.get_total(), 3000); + assert_eq!(pledgeable.get(pledger_1), 0); + assert_eq!(pledgeable.get(pledger_2), 3000); + assert_eq!(pledgeable.get(pledger_3), 0); + + let amount = pledgeable.remove(pledger_2); + + assert_eq!(amount, 3000); + assert_eq!(pledgeable.get_pledger_count(), 0); + assert_eq!(pledgeable.get_total(), 0); + assert_eq!(pledgeable.get(pledger_1), 0); + assert_eq!(pledgeable.get(pledger_2), 0); + assert_eq!(pledgeable.get(pledger_3), 0); + + // pledger_3 not added, so this should do nothing and return 0 + let amount = pledgeable.remove(pledger_3); + + assert_eq!(amount, 0); + assert_eq!(pledgeable.get_pledger_count(), 0); + assert_eq!(pledgeable.get_total(), 0); + assert_eq!(pledgeable.get(pledger_1), 0); + assert_eq!(pledgeable.get(pledger_2), 0); + assert_eq!(pledgeable.get(pledger_3), 0); + } + + #[test] + fn test_remove_first_of_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + // set up 1000 pledgers + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; // actual value set up in the while loop + + let mut i: felt252 = expected_pledger_count.into(); + let first_pledger: ContractAddress = i.try_into().unwrap(); + let first_amount = 100000; + pledgeable.add(first_pledger, first_amount); + expected_total += first_amount; + i -= 1; + + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + expected_total += amount; + i -= 1; + }; + + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + assert_eq!(pledgeable.get(first_pledger), first_amount); + + let removed_amount = pledgeable.remove(first_pledger); + + expected_total -= first_amount; + + assert_eq!(removed_amount, first_amount); + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count - 1); + assert_eq!(pledgeable.get(first_pledger), 0); + } + + #[test] + fn test_remove_middle_of_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + // set up 1000 pledgers + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; // actual value set up in the while loop + + let mut middle_pledger: ContractAddress = Zero::zero(); + let mut middle_amount = 0; + + let mut i: felt252 = expected_pledger_count.into(); + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + expected_total += amount; + + if i == 500 { + middle_pledger = pledger; + middle_amount = amount; + } + + i -= 1; + }; + + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + assert_eq!(pledgeable.get(middle_pledger), middle_amount); + + let removed_amount = pledgeable.remove(middle_pledger); + + expected_total -= middle_amount; + + assert_eq!(removed_amount, middle_amount); + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count - 1); + assert_eq!(pledgeable.get(middle_pledger), 0); + } + + #[test] + fn test_remove_last_of_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + // set up 1000 pledgers + let expected_pledger_count: u32 = 1000; + let mut expected_total: u256 = 0; // actual value set up in the while loop + + let mut i: felt252 = expected_pledger_count.into(); + let last_pledger: ContractAddress = i.try_into().unwrap(); + let last_amount = 100000; + i -= 1; // leave place for the last pledger + + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + expected_total += amount; + i -= 1; + }; + + // add last pledger + pledgeable.add(last_pledger, last_amount); + expected_total += last_amount; + + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); + assert_eq!(pledgeable.get(last_pledger), last_amount); + + let removed_amount = pledgeable.remove(last_pledger); + + expected_total -= last_amount; + + assert_eq!(removed_amount, last_amount); + assert_eq!(pledgeable.get_total(), expected_total); + assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count - 1); + assert_eq!(pledgeable.get(last_pledger), 0); + } + + #[test] + fn test_array() { + let mut pledgeable: TestingState = Default::default(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + + pledgeable.add(pledger_1, 1000); + pledgeable.add(pledger_2, 500); + pledgeable.add(pledger_3, 2500); + // 2nd pledge by pledger_2 *should not* increase the pledge count + pledgeable.add(pledger_2, 1500); + + let pledgers_arr = pledgeable.array(); + + assert_eq!(pledgers_arr.len(), 3); + assert_eq!(pledger_3, *pledgers_arr[0]); + assert_eq!(2500, pledgeable.get(*pledgers_arr[0])); + assert_eq!(pledger_2, *pledgers_arr[1]); + assert_eq!(2000, pledgeable.get(*pledgers_arr[1])); + assert_eq!(pledger_1, *pledgers_arr[2]); + assert_eq!(1000, pledgeable.get(*pledgers_arr[2])); + } + + #[test] + fn test_array_1000_pledgers() { + let mut pledgeable: TestingState = Default::default(); + + // set up 1000 pledgers + let mut pledgers: Array:: = array![]; + let mut i: felt252 = 1000; + while i != 0 { + let pledger: ContractAddress = i.try_into().unwrap(); + let amount: u256 = i.into() * 100; + pledgeable.add(pledger, amount); + pledgers.append(pledger); + i -= 1; + }; + + let pledgers_arr: Array:: = pledgeable.array(); + + assert_eq!(pledgers_arr.len(), pledgers.len()); + + let mut i = 1000; + while let Option::Some(expected_pledger) = pledgers + .pop_front() { + i -= 1; + // pledgers are fetched in reversed order + let actual_pledger: ContractAddress = *pledgers_arr.at(i); + assert_eq!(expected_pledger, actual_pledger); + } + } + + #[test] + fn test_get() { + let mut pledgeable: TestingState = Default::default(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + + pledgeable.add(pledger_1, 1000); + pledgeable.add(pledger_2, 500); + // pledger_3 not added + + assert_eq!(pledgeable.get(pledger_1), 1000); + assert_eq!(pledgeable.get(pledger_2), 500); + assert_eq!(pledgeable.get(pledger_3), 0); + } +} + diff --git a/listings/applications/crowdfunding/src/lib.cairo b/listings/applications/crowdfunding/src/lib.cairo new file mode 100644 index 00000000..3e5429ad --- /dev/null +++ b/listings/applications/crowdfunding/src/lib.cairo @@ -0,0 +1,5 @@ +pub mod campaign; +mod mock_upgrade; + +#[cfg(test)] +mod tests; diff --git a/listings/applications/crowdfunding/src/mock_upgrade.cairo b/listings/applications/crowdfunding/src/mock_upgrade.cairo new file mode 100644 index 00000000..33348d16 --- /dev/null +++ b/listings/applications/crowdfunding/src/mock_upgrade.cairo @@ -0,0 +1,289 @@ +#[starknet::contract] +pub mod MockUpgrade { + use components::ownable::ownable_component::OwnableInternalTrait; + use core::num::traits::zero::Zero; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use starknet::{ + ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, contract_address_const, + get_caller_address, get_contract_address, class_hash::class_hash_const + }; + use components::ownable::ownable_component; + use crowdfunding::campaign::pledgeable::pledgeable_component; + use crowdfunding::campaign::{ICampaign, Details, Campaign::Errors}; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + component!(path: pledgeable_component, storage: pledges, event: PledgeableEvent); + + #[abi(embed_v0)] + pub impl OwnableImpl = ownable_component::Ownable; + impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; + #[abi(embed_v0)] + impl PledgeableImpl = pledgeable_component::Pledgeable; + + #[storage] + struct Storage { + canceled: bool, + claimed: bool, + creator: ContractAddress, + description: ByteArray, + end_time: u64, + goal: u256, + #[substorage(v0)] + ownable: ownable_component::Storage, + #[substorage(v0)] + pledges: pledgeable_component::Storage, + start_time: u64, + title: ByteArray, + token: IERC20Dispatcher, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + Claimed: Claimed, + Canceled: Canceled, + #[flat] + OwnableEvent: ownable_component::Event, + PledgeableEvent: pledgeable_component::Event, + PledgeMade: PledgeMade, + Refunded: Refunded, + RefundedAll: RefundedAll, + Unpledged: Unpledged, + Upgraded: Upgraded, + } + + #[derive(Drop, starknet::Event)] + pub struct Canceled { + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct Claimed { + pub amount: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct PledgeMade { + #[key] + pub pledger: ContractAddress, + pub amount: u256, + } + + #[derive(Drop, starknet::Event)] + pub struct Refunded { + #[key] + pub pledger: ContractAddress, + pub amount: u256, + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct RefundedAll { + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct Unpledged { + #[key] + pub pledger: ContractAddress, + pub amount: u256, + pub reason: ByteArray, + } + + #[derive(Drop, starknet::Event)] + pub struct Upgraded { + pub implementation: ClassHash + } + + const NINETY_DAYS: u64 = consteval_int!(90 * 24 * 60 * 60); + + #[constructor] + fn constructor( + ref self: ContractState, + creator: ContractAddress, + title: ByteArray, + description: ByteArray, + goal: u256, + start_time: u64, + end_time: u64, + token_address: ContractAddress, + ) { + assert(creator.is_non_zero(), Errors::CREATOR_ZERO); + assert(title.len() > 0, Errors::TITLE_EMPTY); + assert(goal > 0, Errors::ZERO_GOAL); + assert(start_time >= get_block_timestamp(), Errors::START_TIME_IN_PAST); + assert(end_time >= start_time, Errors::END_BEFORE_START); + assert(end_time <= get_block_timestamp() + NINETY_DAYS, Errors::END_BIGGER_THAN_MAX); + assert(token_address.is_non_zero(), Errors::ZERO_ADDRESS_TOKEN); + + self.creator.write(creator); + self.title.write(title); + self.goal.write(goal); + self.description.write(description); + self.start_time.write(start_time); + self.end_time.write(end_time); + self.token.write(IERC20Dispatcher { contract_address: token_address }); + self.ownable._init(get_caller_address()); + } + + #[abi(embed_v0)] + impl MockUpgrade of ICampaign { + fn cancel(ref self: ContractState, reason: ByteArray) { + self._assert_only_creator(); + assert(!self.canceled.read(), Errors::CANCELED); + assert(!self.claimed.read(), Errors::CLAIMED); + + self.canceled.write(true); + + self._refund_all(reason.clone()); + + self.emit(Event::Canceled(Canceled { reason })); + } + + fn claim(ref self: ContractState) { + self._assert_only_creator(); + assert(self._is_started(), Errors::NOT_STARTED); + assert(self._is_ended(), Errors::STILL_ACTIVE); + assert(self._is_goal_reached(), Errors::GOAL_NOT_REACHED); + assert(!self.claimed.read(), Errors::CLAIMED); + + let this = get_contract_address(); + let token = self.token.read(); + let amount = token.balance_of(this); + assert(amount > 0, Errors::ZERO_PLEDGES); + + self.claimed.write(true); + + // no need to reset the pledges, as the campaign has ended + // and the data can be used as a testament to how much was raised + + let owner = get_caller_address(); + let success = token.transfer(owner, amount); + assert(success, Errors::TRANSFER_FAILED); + + self.emit(Event::Claimed(Claimed { amount })); + } + + fn get_details(self: @ContractState) -> Details { + Details { + creator: self.creator.read(), + title: self.title.read(), + description: self.description.read(), + goal: self.goal.read(), + start_time: self.start_time.read(), + end_time: self.end_time.read(), + claimed: self.claimed.read(), + canceled: self.canceled.read(), + token: self.token.read().contract_address, + total_pledges: self.pledges.get_total(), + } + } + + fn get_pledge(self: @ContractState, pledger: ContractAddress) -> u256 { + self.pledges.get(pledger) + } + + fn get_pledgers(self: @ContractState) -> Array { + self.pledges.array() + } + + fn pledge(ref self: ContractState, amount: u256) { + assert(self._is_started(), Errors::NOT_STARTED); + assert(!self._is_ended(), Errors::ENDED); + assert(!self.canceled.read(), Errors::CANCELED); + assert(amount > 0, Errors::ZERO_DONATION); + + let pledger = get_caller_address(); + let this = get_contract_address(); + let success = self.token.read().transfer_from(pledger, this, amount); + assert(success, Errors::TRANSFER_FAILED); + + self.pledges.add(pledger, amount); + + self.emit(Event::PledgeMade(PledgeMade { pledger, amount })); + } + + fn refund(ref self: ContractState, pledger: ContractAddress, reason: ByteArray) { + self._assert_only_creator(); + assert(self._is_started(), Errors::NOT_STARTED); + assert(!self.claimed.read(), Errors::CLAIMED); + assert(!self.canceled.read(), Errors::CANCELED); + assert(pledger.is_non_zero(), Errors::ZERO_ADDRESS_PLEDGER); + assert(self.pledges.get(pledger) != 0, Errors::NOTHING_TO_REFUND); + + let amount = self._refund(pledger); + + self.emit(Event::Refunded(Refunded { pledger, amount, reason })) + } + + fn unpledge(ref self: ContractState, reason: ByteArray) { + assert(self._is_started(), Errors::NOT_STARTED); + assert(!self._is_goal_reached(), Errors::PLEDGES_LOCKED); + assert(self.pledges.get(get_caller_address()) != 0, Errors::NOTHING_TO_UNPLEDGE); + + let pledger = get_caller_address(); + let amount = self._refund(pledger); + + self.emit(Event::Unpledged(Unpledged { pledger, amount, reason })); + } + + fn upgrade(ref self: ContractState, impl_hash: ClassHash, new_end_time: Option) { + self.ownable._assert_only_owner(); + assert(impl_hash.is_non_zero(), Errors::CLASS_HASH_ZERO); + + // only active campaigns have funds to refund and an end time to update + if self._is_started() { + if let Option::Some(end_time) = new_end_time { + assert(end_time >= get_block_timestamp(), Errors::END_BEFORE_NOW); + assert( + end_time <= get_block_timestamp() + NINETY_DAYS, Errors::END_BIGGER_THAN_MAX + ); + self.end_time.write(end_time); + }; + self._refund_all("contract upgraded"); + } + + starknet::syscalls::replace_class_syscall(impl_hash).unwrap_syscall(); + + self.emit(Event::Upgraded(Upgraded { implementation: impl_hash })); + } + } + + #[generate_trait] + impl MockUpgradeInternalImpl of MockUpgradeInternalTrait { + fn _assert_only_creator(self: @ContractState) { + let caller = get_caller_address(); + assert(caller.is_non_zero(), Errors::ZERO_ADDRESS_CALLER); + assert(caller == self.creator.read(), Errors::NOT_CREATOR); + } + + fn _is_ended(self: @ContractState) -> bool { + get_block_timestamp() >= self.end_time.read() + } + + fn _is_goal_reached(self: @ContractState) -> bool { + self.pledges.get_total() >= self.goal.read() + } + + fn _is_started(self: @ContractState) -> bool { + get_block_timestamp() >= self.start_time.read() + } + + fn _refund(ref self: ContractState, pledger: ContractAddress) -> u256 { + let amount = self.pledges.remove(pledger); + + let success = self.token.read().transfer(pledger, amount); + assert(success, Errors::TRANSFER_FAILED); + + amount + } + + fn _refund_all(ref self: ContractState, reason: ByteArray) { + let mut pledges = self.pledges.array(); + while let Option::Some(pledger) = pledges.pop_front() { + self._refund(pledger); + }; + self.emit(Event::RefundedAll(RefundedAll { reason })); + } + } +} diff --git a/listings/applications/crowdfunding/src/tests.cairo b/listings/applications/crowdfunding/src/tests.cairo new file mode 100644 index 00000000..fde363c2 --- /dev/null +++ b/listings/applications/crowdfunding/src/tests.cairo @@ -0,0 +1,480 @@ +use core::traits::TryInto; +use core::clone::Clone; +use core::result::ResultTrait; +use starknet::{ + ContractAddress, ClassHash, get_block_timestamp, contract_address_const, get_caller_address, +}; +use snforge_std::{ + declare, ContractClass, ContractClassTrait, start_cheat_caller_address, + stop_cheat_caller_address, spy_events, SpyOn, EventSpy, EventAssertions, get_class_hash, + cheat_block_timestamp_global +}; + +use crowdfunding::campaign::{Campaign, ICampaignDispatcher, ICampaignDispatcherTrait}; +use components::ownable::{IOwnableDispatcher, IOwnableDispatcherTrait}; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + +/// Deploy a campaign contract with the provided data +fn deploy( + contract: ContractClass, + title: ByteArray, + description: ByteArray, + goal: u256, + start_time: u64, + end_time: u64, + token: ContractAddress +) -> ICampaignDispatcher { + let creator = contract_address_const::<'creator'>(); + let mut calldata: Array:: = array![]; + ((creator, title, description, goal), start_time, end_time, token).serialize(ref calldata); + + let contract_address = contract.precalculate_address(@calldata); + let owner = contract_address_const::<'owner'>(); + start_cheat_caller_address(contract_address, owner); + + contract.deploy(@calldata).unwrap(); + + stop_cheat_caller_address(contract_address); + + ICampaignDispatcher { contract_address } +} + +fn deploy_with_token( + contract: ContractClass, token: ContractClass +) -> (ICampaignDispatcher, IERC20Dispatcher) { + // define ERC20 data + let token_name: ByteArray = "My Token"; + let token_symbol: ByteArray = "MTKN"; + let token_supply: u256 = 100000; + let token_owner = contract_address_const::<'token_owner'>(); + let token_recipient = token_owner; + + // deploy ERC20 token + let mut token_constructor_calldata = array![]; + ((token_name, token_symbol, token_supply, token_recipient), token_owner) + .serialize(ref token_constructor_calldata); + let (token_address, _) = token.deploy(@token_constructor_calldata).unwrap(); + + // transfer amounts to some pledgers + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + + start_cheat_caller_address(token_address, token_owner); + let token_dispatcher = IERC20Dispatcher { contract_address: token_address }; + token_dispatcher.transfer(pledger_1, 10000); + token_dispatcher.transfer(pledger_2, 10000); + token_dispatcher.transfer(pledger_3, 10000); + + // deploy the actual Campaign contract + let start_time = get_block_timestamp(); + let end_time = start_time + 60; + let campaign_dispatcher = deploy( + contract, "title 1", "description 1", 10000, start_time, end_time, token_address + ); + + // approve the pledges for each pledger + start_cheat_caller_address(token_address, pledger_1); + token_dispatcher.approve(campaign_dispatcher.contract_address, 10000); + start_cheat_caller_address(token_address, pledger_2); + token_dispatcher.approve(campaign_dispatcher.contract_address, 10000); + start_cheat_caller_address(token_address, pledger_3); + token_dispatcher.approve(campaign_dispatcher.contract_address, 10000); + + // NOTE: don't forget to stop the caller address cheat on the ERC20 contract!! + // Otherwise, any call to this contract from any source will have the cheated + // address as the caller + stop_cheat_caller_address(token_address); + + (campaign_dispatcher, token_dispatcher) +} + +#[test] +fn test_deploy() { + let start_time = get_block_timestamp(); + let end_time = start_time + 60; + let contract = declare("Campaign").unwrap(); + let campaign = deploy( + contract, + "title 1", + "description 1", + 10000, + start_time, + end_time, + contract_address_const::<'token'>() + ); + + let details = campaign.get_details(); + assert_eq!(details.title, "title 1"); + assert_eq!(details.description, "description 1"); + assert_eq!(details.goal, 10000); + assert_eq!(details.start_time, start_time); + assert_eq!(details.end_time, end_time); + assert_eq!(details.claimed, false); + assert_eq!(details.canceled, false); + assert_eq!(details.token, contract_address_const::<'token'>()); + assert_eq!(details.total_pledges, 0); + assert_eq!(details.creator, contract_address_const::<'creator'>()); + + let owner: ContractAddress = contract_address_const::<'owner'>(); + let campaign_ownable = IOwnableDispatcher { contract_address: campaign.contract_address }; + assert_eq!(campaign_ownable.owner(), owner); +} + +#[test] +fn test_successful_campaign() { + let token_class = declare("ERC20Upgradeable").unwrap(); + let contract_class = declare("Campaign").unwrap(); + let (campaign, token) = deploy_with_token(contract_class, token_class); + + let creator = contract_address_const::<'creator'>(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + + // 1st donation + start_cheat_caller_address(campaign.contract_address, pledger_1); + let mut prev_balance = token.balance_of(pledger_1); + campaign.pledge(3000); + assert_eq!(campaign.get_details().total_pledges, 3000); + assert_eq!(campaign.get_pledge(pledger_1), 3000); + assert_eq!(token.balance_of(pledger_1), prev_balance - 3000); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::PledgeMade( + Campaign::PledgeMade { pledger: pledger_1, amount: 3000 } + ) + ) + ] + ); + + // 2nd donation + start_cheat_caller_address(campaign.contract_address, pledger_2); + prev_balance = token.balance_of(pledger_2); + campaign.pledge(500); + assert_eq!(campaign.get_details().total_pledges, 3500); + assert_eq!(campaign.get_pledge(pledger_2), 500); + assert_eq!(token.balance_of(pledger_2), prev_balance - 500); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::PledgeMade( + Campaign::PledgeMade { pledger: pledger_2, amount: 500 } + ) + ) + ] + ); + + // 3rd donation + start_cheat_caller_address(campaign.contract_address, pledger_3); + prev_balance = token.balance_of(pledger_3); + campaign.pledge(7000); + assert_eq!(campaign.get_details().total_pledges, 10500); + assert_eq!(campaign.get_pledge(pledger_3), 7000); + assert_eq!(token.balance_of(pledger_3), prev_balance - 7000); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::PledgeMade( + Campaign::PledgeMade { pledger: pledger_3, amount: 7000 } + ) + ) + ] + ); + + // claim + cheat_block_timestamp_global(campaign.get_details().end_time); + start_cheat_caller_address(campaign.contract_address, creator); + prev_balance = token.balance_of(creator); + campaign.claim(); + assert_eq!(token.balance_of(creator), prev_balance + 10500); + assert!(campaign.get_details().claimed); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::Claimed(Campaign::Claimed { amount: 10500 }) + ) + ] + ); +} + +#[test] +fn test_upgrade_class_hash() { + let new_class_hash = declare("MockUpgrade").unwrap().class_hash; + let owner = contract_address_const::<'owner'>(); + + // test pending campaign + let contract_class = declare("Campaign").unwrap(); + let token_class = declare("ERC20Upgradeable").unwrap(); + let (campaign, _) = deploy_with_token(contract_class, token_class); + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + + start_cheat_caller_address(campaign.contract_address, owner); + campaign.upgrade(new_class_hash, Option::None); + stop_cheat_caller_address(campaign.contract_address); + + assert_eq!(get_class_hash(campaign.contract_address), new_class_hash); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::Upgraded(Campaign::Upgraded { implementation: new_class_hash }) + ) + ] + ); + + // test active campaign + let (campaign, token) = deploy_with_token(contract_class, token_class); + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let duration: u64 = 60; + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + let prev_balance_pledger_1 = token.balance_of(pledger_1); + let prev_balance_pledger_2 = token.balance_of(pledger_2); + let prev_balance_pledger_3 = token.balance_of(pledger_3); + + start_cheat_caller_address(campaign.contract_address, pledger_1); + campaign.pledge(3000); + start_cheat_caller_address(campaign.contract_address, pledger_2); + campaign.pledge(1000); + start_cheat_caller_address(campaign.contract_address, pledger_3); + campaign.pledge(2000); + + start_cheat_caller_address(campaign.contract_address, owner); + campaign.upgrade(new_class_hash, Option::Some(duration)); + stop_cheat_caller_address(campaign.contract_address); + + assert_eq!(prev_balance_pledger_1, token.balance_of(pledger_1)); + assert_eq!(prev_balance_pledger_2, token.balance_of(pledger_2)); + assert_eq!(prev_balance_pledger_3, token.balance_of(pledger_3)); + assert_eq!(campaign.get_details().total_pledges, 0); + assert_eq!(campaign.get_details().end_time, get_block_timestamp() + duration); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::Upgraded(Campaign::Upgraded { implementation: new_class_hash }) + ), + ( + campaign.contract_address, + Campaign::Event::RefundedAll( + Campaign::RefundedAll { reason: "contract upgraded" } + ) + ) + ] + ); +} + +#[test] +fn test_cancel() { + let contract_class = declare("Campaign").unwrap(); + let token_class = declare("ERC20Upgradeable").unwrap(); + + // test canceled campaign + let (campaign, token) = deploy_with_token(contract_class, token_class); + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let creator = contract_address_const::<'creator'>(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + let pledge_1: u256 = 3000; + let pledge_2: u256 = 3000; + let pledge_3: u256 = 3000; + let prev_balance_pledger_1 = token.balance_of(pledger_1); + let prev_balance_pledger_2 = token.balance_of(pledger_2); + let prev_balance_pledger_3 = token.balance_of(pledger_3); + + start_cheat_caller_address(campaign.contract_address, pledger_1); + campaign.pledge(pledge_1); + start_cheat_caller_address(campaign.contract_address, pledger_2); + campaign.pledge(pledge_2); + start_cheat_caller_address(campaign.contract_address, pledger_3); + campaign.pledge(pledge_3); + assert_eq!(campaign.get_details().total_pledges, pledge_1 + pledge_2 + pledge_3); + assert_eq!(token.balance_of(pledger_1), prev_balance_pledger_1 - pledge_1); + assert_eq!(token.balance_of(pledger_2), prev_balance_pledger_2 - pledge_2); + assert_eq!(token.balance_of(pledger_3), prev_balance_pledger_3 - pledge_3); + + start_cheat_caller_address(campaign.contract_address, creator); + campaign.cancel("testing"); + stop_cheat_caller_address(campaign.contract_address); + + assert_eq!(prev_balance_pledger_1, token.balance_of(pledger_1)); + assert_eq!(prev_balance_pledger_2, token.balance_of(pledger_2)); + assert_eq!(prev_balance_pledger_3, token.balance_of(pledger_3)); + assert_eq!(campaign.get_details().total_pledges, 0); + assert!(campaign.get_details().canceled); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::RefundedAll(Campaign::RefundedAll { reason: "testing" }) + ), + ( + campaign.contract_address, + Campaign::Event::Canceled(Campaign::Canceled { reason: "testing" }) + ) + ] + ); + + // test failed campaign + let (campaign, token) = deploy_with_token(contract_class, token_class); + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let creator = contract_address_const::<'creator'>(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let pledger_3 = contract_address_const::<'pledger_3'>(); + let pledge_1: u256 = 3000; + let pledge_2: u256 = 3000; + let pledge_3: u256 = 3000; + let prev_balance_pledger_1 = token.balance_of(pledger_1); + let prev_balance_pledger_2 = token.balance_of(pledger_2); + let prev_balance_pledger_3 = token.balance_of(pledger_3); + + start_cheat_caller_address(campaign.contract_address, pledger_1); + campaign.pledge(pledge_1); + start_cheat_caller_address(campaign.contract_address, pledger_2); + campaign.pledge(pledge_2); + start_cheat_caller_address(campaign.contract_address, pledger_3); + campaign.pledge(pledge_3); + assert_eq!(campaign.get_details().total_pledges, pledge_1 + pledge_2 + pledge_3); + assert_eq!(token.balance_of(pledger_1), prev_balance_pledger_1 - pledge_1); + assert_eq!(token.balance_of(pledger_2), prev_balance_pledger_2 - pledge_2); + assert_eq!(token.balance_of(pledger_3), prev_balance_pledger_3 - pledge_3); + + cheat_block_timestamp_global(campaign.get_details().end_time); + + start_cheat_caller_address(campaign.contract_address, creator); + campaign.cancel("testing"); + stop_cheat_caller_address(campaign.contract_address); + + assert_eq!(prev_balance_pledger_1, token.balance_of(pledger_1)); + assert_eq!(prev_balance_pledger_2, token.balance_of(pledger_2)); + assert_eq!(prev_balance_pledger_3, token.balance_of(pledger_3)); + assert_eq!(campaign.get_details().total_pledges, 0); + assert!(campaign.get_details().canceled); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::RefundedAll(Campaign::RefundedAll { reason: "testing" }) + ), + ( + campaign.contract_address, + Campaign::Event::Canceled(Campaign::Canceled { reason: "testing" }) + ) + ] + ); +} + +#[test] +fn test_refund() { + // setup + let (campaign, token) = deploy_with_token( + declare("Campaign").unwrap(), declare("ERC20Upgradeable").unwrap() + ); + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let creator = contract_address_const::<'creator'>(); + let pledger_1 = contract_address_const::<'pledger_1'>(); + let pledger_2 = contract_address_const::<'pledger_2'>(); + let amount_1: u256 = 3000; + let amount_2: u256 = 1500; + let prev_balance_1 = token.balance_of(pledger_1); + let prev_balance_2 = token.balance_of(pledger_2); + + // donate + start_cheat_caller_address(campaign.contract_address, pledger_1); + campaign.pledge(amount_1); + assert_eq!(campaign.get_details().total_pledges, amount_1); + assert_eq!(campaign.get_pledge(pledger_1), amount_1); + assert_eq!(token.balance_of(pledger_1), prev_balance_1 - amount_1); + + start_cheat_caller_address(campaign.contract_address, pledger_2); + campaign.pledge(amount_2); + assert_eq!(campaign.get_details().total_pledges, amount_1 + amount_2); + assert_eq!(campaign.get_pledge(pledger_2), amount_2); + assert_eq!(token.balance_of(pledger_2), prev_balance_2 - amount_2); + + // refund + start_cheat_caller_address(campaign.contract_address, creator); + campaign.refund(pledger_1, "testing"); + assert_eq!(campaign.get_details().total_pledges, amount_2); + assert_eq!(campaign.get_pledge(pledger_2), amount_2); + assert_eq!(token.balance_of(pledger_2), prev_balance_2 - amount_2); + assert_eq!(token.balance_of(pledger_1), prev_balance_1); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::Refunded( + Campaign::Refunded { + pledger: pledger_1, amount: amount_1, reason: "testing" + } + ) + ) + ] + ); +} + +#[test] +fn test_unpledge() { + // setup + let (campaign, token) = deploy_with_token( + declare("Campaign").unwrap(), declare("ERC20Upgradeable").unwrap() + ); + let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let pledger = contract_address_const::<'pledger_1'>(); + let amount: u256 = 3000; + let prev_balance = token.balance_of(pledger); + + // donate + start_cheat_caller_address(campaign.contract_address, pledger); + campaign.pledge(amount); + assert_eq!(campaign.get_details().total_pledges, amount); + assert_eq!(campaign.get_pledge(pledger), amount); + assert_eq!(token.balance_of(pledger), prev_balance - amount); + + // unpledge + campaign.unpledge("testing"); + assert_eq!(campaign.get_details().total_pledges, 0); + assert_eq!(campaign.get_pledge(pledger), 0); + assert_eq!(token.balance_of(pledger), prev_balance); + + spy + .assert_emitted( + @array![ + ( + campaign.contract_address, + Campaign::Event::Unpledged( + Campaign::Unpledged { pledger, amount, reason: "testing" } + ) + ) + ] + ); +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index d642f9be..07551165 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -60,6 +60,8 @@ Summary - [TimeLock](./applications/timelock.md) - [Staking](./applications/staking.md) - [Simple Storage with Starknet-js](./applications/simple_storage_starknetjs.md) +- [Crowdfunding Campaign](./applications/crowdfunding.md) +- [AdvancedFactory: Crowdfunding](./applications/advanced_factory.md) diff --git a/src/applications/advanced_factory.md b/src/applications/advanced_factory.md new file mode 100644 index 00000000..5d2a27fd --- /dev/null +++ b/src/applications/advanced_factory.md @@ -0,0 +1,13 @@ +# AdvancedFactory: Crowdfunding + +This is an example of an advanced factory contract that manages crowdfunding Campaign contracts created in the ["Crowdfunding" chapter](./crowdfunding.md). The advanced factory allows for a centralized creation and management of `Campaign` contracts on the Starknet blockchain, ensuring that they adhere to a standard interface and can be easily upgraded. + +Key Features +1. **Campaign Creation**: Users can create new crowdfunding campaigns with specific details such as title, description, goal, and duration. +2. **Campaign Management**: The factory contract stores and manages the campaigns, allowing for upgrades and tracking. +3. **Upgrade Mechanism**: The factory owner can update the implementation of the campaign contract, ensuring that all campaigns benefit from improvements and bug fixes. + - the factory only updates it's `Campaign` class hash and emits an event to notify any listeners, but the `Campaign` creators are in the end responsible for actually upgrading their contracts. + +```rust +{{#include ../../listings/applications/advanced_factory/src/contract.cairo:contract}} +``` diff --git a/src/applications/crowdfunding.md b/src/applications/crowdfunding.md new file mode 100644 index 00000000..8d78f05a --- /dev/null +++ b/src/applications/crowdfunding.md @@ -0,0 +1,26 @@ +# Crowdfunding Campaign + +Crowdfunding is a method of raising capital through the collective effort of many individuals. It allows project creators to raise funds from a large number of people, usually through small contributions. + +1. Contract admin creates a campaign in some user's name (i.e. creator). +2. Users can pledge, transferring their token to a campaign. +3. Users can "unpledge", retrieving their tokens. +4. The creator can at any point refund any of the users. +5. Once the total amount pledged is more than the campaign goal, the campaign funds are "locked" in the contract, meaning the users can no longer unpledge; they can still pledge though. +6. After the campaign ends, the campaign creator can claim the funds if the campaign goal is reached. +7. Otherwise, campaign did not reach it's goal, pledgers can retrieve their funds. +8. The creator can at any point cancel the campaign for whatever reason and refund all of the pledgers. +9. The contract admin can upgrade the contract implementation, refunding all of the users and reseting the campaign state (we will use this in the [Advanced Factory chapter](./advanced_factory.md)). + +Because contract upgrades need to be able to refund all of the pledges, we need to be able to iterate over all of the pledgers and their amounts. Since iteration is not supported by `LegacyMap`, we need to create a custom storage type that will encompass pledge management. We use a component for this purpose. + +```rust +{{#include ../../listings/applications/crowdfunding/src/campaign/pledgeable.cairo:component}} +``` + +Now we can create the `Campaign` contract. + + +```rust +{{#include ../../listings/applications/crowdfunding/src/campaign.cairo:contract}} +``` From 6f4d055f74438ed9a1c81944590f3e22370d5ea7 Mon Sep 17 00:00:00 2001 From: Emmanuel A Akalo <124416278+NueloSE@users.noreply.github.com> Date: Mon, 1 Jul 2024 07:42:42 +0100 Subject: [PATCH 22/36] test: countable (switchable, ownable) components (#205) * test: implement test for countable component * test: implement test for switchable component * test: implement test for ownable component * chore: implement test for emitted events and add anchor tags * feat: apply requested changes * feat: apply requested changes * feat: revisions --------- Co-authored-by: julio4 --- .../components/src/contracts.cairo | 6 - .../components/src/contracts/owned.cairo | 174 ------------------ .../components/src/contracts/switch.cairo | 84 --------- .../src/contracts/switch_collision.cairo | 49 ----- .../components/src/contracts/tests.cairo | 1 - .../tests/switch_collision_tests.cairo | 45 ----- .../components/src/countable.cairo | 63 +++++++ .../applications/components/src/lib.cairo | 3 +- .../applications/components/src/others.cairo | 1 + .../src/others/switch_collision.cairo | 93 ++++++++++ .../applications/components/src/ownable.cairo | 156 +++++++++++++++- .../components/src/switchable.cairo | 86 ++++++++- scripts/cairo_programs_verifier.sh | 2 +- src/components/collisions.md | 8 +- src/components/dependencies.md | 2 +- src/components/how_to.md | 13 +- src/components/ownable.md | 4 +- 17 files changed, 409 insertions(+), 381 deletions(-) delete mode 100644 listings/applications/components/src/contracts.cairo delete mode 100644 listings/applications/components/src/contracts/owned.cairo delete mode 100644 listings/applications/components/src/contracts/switch.cairo delete mode 100644 listings/applications/components/src/contracts/switch_collision.cairo delete mode 100644 listings/applications/components/src/contracts/tests.cairo delete mode 100644 listings/applications/components/src/contracts/tests/switch_collision_tests.cairo create mode 100644 listings/applications/components/src/others.cairo create mode 100644 listings/applications/components/src/others/switch_collision.cairo diff --git a/listings/applications/components/src/contracts.cairo b/listings/applications/components/src/contracts.cairo deleted file mode 100644 index 48f48bb4..00000000 --- a/listings/applications/components/src/contracts.cairo +++ /dev/null @@ -1,6 +0,0 @@ -mod switch; -mod switch_collision; -mod owned; - -#[cfg(test)] -mod tests; diff --git a/listings/applications/components/src/contracts/owned.cairo b/listings/applications/components/src/contracts/owned.cairo deleted file mode 100644 index 3eacbdd8..00000000 --- a/listings/applications/components/src/contracts/owned.cairo +++ /dev/null @@ -1,174 +0,0 @@ -// ANCHOR: contract -#[starknet::interface] -pub trait IOwned { - fn do_something(ref self: TContractState); -} - -#[starknet::contract] -pub mod OwnedContract { - use components::ownable::{IOwnable, ownable_component, ownable_component::OwnableInternalTrait}; - - component!(path: ownable_component, storage: ownable, event: OwnableEvent); - - #[abi(embed_v0)] - impl OwnableImpl = ownable_component::Ownable; - impl OwnableInternalImpl = ownable_component::OwnableInternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - ownable: ownable_component::Storage, - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.ownable._init(starknet::get_caller_address()); - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - OwnableEvent: ownable_component::Event, - } - - #[abi(embed_v0)] - impl Owned of super::IOwned { - fn do_something(ref self: ContractState) { - self.ownable._assert_only_owner(); - // ... - } - } -} -// ANCHOR_END: contract - -#[cfg(test)] -mod tests { - use core::num::traits::Zero; - use super::{OwnedContract, IOwnedDispatcher, IOwnedDispatcherTrait}; - use components::ownable::{IOwnable, IOwnableDispatcher, IOwnableDispatcherTrait}; - - use starknet::{contract_address_const, ContractAddress}; - use starknet::testing::{set_caller_address, set_contract_address}; - use starknet::storage::StorageMemberAccessTrait; - use starknet::SyscallResultTrait; - use starknet::syscalls::deploy_syscall; - - fn deploy() -> (IOwnedDispatcher, IOwnableDispatcher) { - let (contract_address, _) = deploy_syscall( - OwnedContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false - ) - .unwrap_syscall(); - - (IOwnedDispatcher { contract_address }, IOwnableDispatcher { contract_address },) - } - - #[test] - #[available_gas(2000000)] - fn test_init() { - let owner = contract_address_const::<'owner'>(); - set_contract_address(owner); - let (_, ownable) = deploy(); - - assert(ownable.owner() == owner, 'wrong_owner'); - } - - #[test] - #[available_gas(2000000)] - fn test_wrong_owner() { - set_contract_address(contract_address_const::<'owner'>()); - let (_, ownable) = deploy(); - - let not_owner = contract_address_const::<'not_owner'>(); - assert(ownable.owner() != not_owner, 'wrong_owner'); - } - - #[test] - #[available_gas(2000000)] - fn test_do_something() { - set_contract_address(contract_address_const::<'owner'>()); - let (contract, _) = deploy(); - - contract.do_something(); - // Should not panic - } - - #[test] - #[available_gas(2000000)] - #[should_panic] - fn test_do_something_not_owner() { - set_contract_address(contract_address_const::<'owner'>()); - let (contract, _) = deploy(); - - set_contract_address(contract_address_const::<'not_owner'>()); - contract.do_something(); - } - - #[test] - #[available_gas(2000000)] - fn test_transfer_ownership() { - set_contract_address(contract_address_const::<'initial'>()); - let (contract, ownable) = deploy(); - - let new_owner = contract_address_const::<'new_owner'>(); - ownable.transfer_ownership(new_owner); - - assert(ownable.owner() == new_owner, 'wrong_owner'); - - set_contract_address(new_owner); - contract.do_something(); - } - - #[test] - #[available_gas(2000000)] - #[should_panic] - fn test_transfer_ownership_not_owner() { - set_contract_address(contract_address_const::<'initial'>()); - let (_, ownable) = deploy(); - - set_contract_address(contract_address_const::<'not_owner'>()); - ownable.transfer_ownership(contract_address_const::<'new_owner'>()); - } - - #[test] - #[available_gas(2000000)] - #[should_panic] - fn test_transfer_ownership_zero_error() { - set_contract_address(contract_address_const::<'initial'>()); - let (_, ownable) = deploy(); - - ownable.transfer_ownership(Zero::zero()); - } - - #[test] - #[available_gas(2000000)] - fn test_renounce_ownership() { - set_contract_address(contract_address_const::<'owner'>()); - let (_, ownable) = deploy(); - - ownable.renounce_ownership(); - assert(ownable.owner() == Zero::zero(), 'not_zero_owner'); - } - - #[test] - #[available_gas(2000000)] - #[should_panic] - fn test_renounce_ownership_not_owner() { - set_contract_address(contract_address_const::<'owner'>()); - let (_, ownable) = deploy(); - - set_contract_address(contract_address_const::<'not_owner'>()); - ownable.renounce_ownership(); - } - - #[test] - #[available_gas(2000000)] - #[should_panic] - fn test_renounce_ownership_previous_owner() { - set_contract_address(contract_address_const::<'owner'>()); - let (contract, ownable) = deploy(); - - ownable.renounce_ownership(); - - contract.do_something(); - } -} diff --git a/listings/applications/components/src/contracts/switch.cairo b/listings/applications/components/src/contracts/switch.cairo deleted file mode 100644 index e6219d12..00000000 --- a/listings/applications/components/src/contracts/switch.cairo +++ /dev/null @@ -1,84 +0,0 @@ -// ANCHOR: contract -#[starknet::contract] -pub mod SwitchContract { - use components::switchable::switchable_component; - - component!(path: switchable_component, storage: switch, event: SwitchableEvent); - - #[abi(embed_v0)] - impl SwitchableImpl = switchable_component::Switchable; - impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl; - - #[storage] - struct Storage { - #[substorage(v0)] - switch: switchable_component::Storage, - } - - #[constructor] - fn constructor(ref self: ContractState) { - self.switch._off(); - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - SwitchableEvent: switchable_component::Event, - } -} -// ANCHOR_END: contract - -#[cfg(test)] -mod tests { - use components::switchable::switchable_component::SwitchableInternalTrait; - use components::switchable::ISwitchable; - - use starknet::storage::StorageMemberAccessTrait; - use super::SwitchContract; - - fn STATE() -> SwitchContract::ContractState { - SwitchContract::contract_state_for_testing() - } - - #[test] - #[available_gas(2000000)] - fn test_init() { - let state = STATE(); - assert(state.is_on() == false, 'The switch should be off'); - } - - #[test] - #[available_gas(2000000)] - fn test_switch() { - let mut state = STATE(); - - state.switch(); - assert(state.is_on() == true, 'The switch should be on'); - - state.switch(); - assert(state.is_on() == false, 'The switch should be off'); - } - - #[test] - #[available_gas(2000000)] - fn test_value() { - let mut state = STATE(); - assert(state.is_on() == state.switch.switchable_value.read(), 'Wrong value'); - - state.switch.switch(); - assert(state.is_on() == state.switch.switchable_value.read(), 'Wrong value'); - } - - #[test] - #[available_gas(2000000)] - fn test_internal_off() { - let mut state = STATE(); - - state.switch._off(); - assert(state.is_on() == false, 'The switch should be off'); - - state.switch(); - state.switch._off(); - assert(state.is_on() == false, 'The switch should be off'); - } -} diff --git a/listings/applications/components/src/contracts/switch_collision.cairo b/listings/applications/components/src/contracts/switch_collision.cairo deleted file mode 100644 index fa8f36c9..00000000 --- a/listings/applications/components/src/contracts/switch_collision.cairo +++ /dev/null @@ -1,49 +0,0 @@ -// ANCHOR: interface -#[starknet::interface] -pub trait ISwitchCollision { - fn set(ref self: TContractState, value: bool); - fn get(ref self: TContractState) -> bool; -} -// ANCHOR_END: interface - -#[starknet::contract] -pub mod SwitchCollisionContract { - use components::switchable::switchable_component; - - component!(path: switchable_component, storage: switch, event: SwitchableEvent); - - #[abi(embed_v0)] - impl SwitchableImpl = switchable_component::Switchable; - impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl; - - // ANCHOR: storage - #[storage] - struct Storage { - switchable_value: bool, - #[substorage(v0)] - switch: switchable_component::Storage, - } - // ANCHOR_END: storage - - #[constructor] - fn constructor(ref self: ContractState) { - self.switch._off(); - } - - #[event] - #[derive(Drop, starknet::Event)] - enum Event { - SwitchableEvent: switchable_component::Event, - } - - #[abi(embed_v0)] - impl SwitchCollisionContract of super::ISwitchCollision { - fn set(ref self: ContractState, value: bool) { - self.switchable_value.write(value); - } - - fn get(ref self: ContractState) -> bool { - self.switchable_value.read() - } - } -} diff --git a/listings/applications/components/src/contracts/tests.cairo b/listings/applications/components/src/contracts/tests.cairo deleted file mode 100644 index 8b3bd47d..00000000 --- a/listings/applications/components/src/contracts/tests.cairo +++ /dev/null @@ -1 +0,0 @@ -mod switch_collision_tests; diff --git a/listings/applications/components/src/contracts/tests/switch_collision_tests.cairo b/listings/applications/components/src/contracts/tests/switch_collision_tests.cairo deleted file mode 100644 index 9ffe848e..00000000 --- a/listings/applications/components/src/contracts/tests/switch_collision_tests.cairo +++ /dev/null @@ -1,45 +0,0 @@ -mod switch_collision_tests { - use components::switchable::switchable_component::SwitchableInternalTrait; - use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait}; - - use components::contracts::switch_collision::{ - SwitchCollisionContract, ISwitchCollisionDispatcher, ISwitchCollisionDispatcherTrait - }; - - use starknet::storage::StorageMemberAccessTrait; - use starknet::SyscallResultTrait; - use starknet::syscalls::deploy_syscall; - - fn deploy() -> (ISwitchCollisionDispatcher, ISwitchableDispatcher) { - let (contract_address, _) = deploy_syscall( - SwitchCollisionContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false - ) - .unwrap_syscall(); - - ( - ISwitchCollisionDispatcher { contract_address }, - ISwitchableDispatcher { contract_address }, - ) - } - - #[test] - #[available_gas(2000000)] - // ANCHOR: collision - fn test_collision() { - let (mut contract, mut contract_iswitch) = deploy(); - - assert(contract.get() == false, 'value !off'); - assert(contract_iswitch.is_on() == false, 'switch !off'); - - contract_iswitch.switch(); - assert(contract_iswitch.is_on() == true, 'switch !on'); - assert(contract.get() == true, 'value !on'); - - // `collision` between component storage 'value' and contract storage 'value' - assert(contract.get() == contract_iswitch.is_on(), 'value != switch'); - - contract.set(false); - assert(contract.get() == contract_iswitch.is_on(), 'value != switch'); - } -// ANCHOR_END: collision -} diff --git a/listings/applications/components/src/countable.cairo b/listings/applications/components/src/countable.cairo index bafcd6ca..6ca26c63 100644 --- a/listings/applications/components/src/countable.cairo +++ b/listings/applications/components/src/countable.cairo @@ -1,3 +1,4 @@ +// ANCHOR: component #[starknet::interface] pub trait ICountable { fn get(self: @TContractState) -> u32; @@ -24,3 +25,65 @@ pub mod countable_component { } } } +// ANCHOR_END: component + +#[starknet::contract] +mod CountableContract { + use super::countable_component; + + component!(path: countable_component, storage: countable, event: CountableEvent); + + #[storage] + struct Storage { + #[substorage(v0)] + countable: countable_component::Storage, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + CountableEvent: countable_component::Event + } + + #[abi(embed_v0)] + impl CountableImpl = countable_component::Countable; +} + + +#[cfg(test)] +mod test { + use super::CountableContract; + use super::{ICountableDispatcher, ICountableDispatcherTrait}; + use starknet::syscalls::deploy_syscall; + use starknet::SyscallResultTrait; + + fn deploy_countable() -> ICountableDispatcher { + let (address, _) = deploy_syscall( + CountableContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + ICountableDispatcher { contract_address: address } + } + + #[test] + fn test_constructor() { + let counter = deploy_countable(); + assert_eq!(counter.get(), 0); + } + + #[test] + fn test_increment() { + let counter = deploy_countable(); + counter.increment(); + assert_eq!(counter.get(), 1); + } + + #[test] + fn test_multiple_increments() { + let counter = deploy_countable(); + counter.increment(); + counter.increment(); + counter.increment(); + assert_eq!(counter.get(), 3); + } +} diff --git a/listings/applications/components/src/lib.cairo b/listings/applications/components/src/lib.cairo index 1c49f9d4..450e211a 100644 --- a/listings/applications/components/src/lib.cairo +++ b/listings/applications/components/src/lib.cairo @@ -3,4 +3,5 @@ pub mod switchable; pub mod countable; pub mod ownable; -mod contracts; +// Not components but using them for specific examples +mod others; diff --git a/listings/applications/components/src/others.cairo b/listings/applications/components/src/others.cairo new file mode 100644 index 00000000..d79c3831 --- /dev/null +++ b/listings/applications/components/src/others.cairo @@ -0,0 +1 @@ +mod switch_collision; diff --git a/listings/applications/components/src/others/switch_collision.cairo b/listings/applications/components/src/others/switch_collision.cairo new file mode 100644 index 00000000..58b2973f --- /dev/null +++ b/listings/applications/components/src/others/switch_collision.cairo @@ -0,0 +1,93 @@ +// ANCHOR: interface +#[starknet::interface] +pub trait ISwitchCollision { + fn set(ref self: TContractState, value: bool); + fn get(ref self: TContractState) -> bool; +} +// ANCHOR_END: interface + +#[starknet::contract] +pub mod SwitchCollisionContract { + use components::switchable::switchable_component; + + component!(path: switchable_component, storage: switch, event: SwitchableEvent); + + #[abi(embed_v0)] + impl SwitchableImpl = switchable_component::Switchable; + impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl; + + // ANCHOR: storage + #[storage] + struct Storage { + switchable_value: bool, + #[substorage(v0)] + switch: switchable_component::Storage, + } + // ANCHOR_END: storage + + #[constructor] + fn constructor(ref self: ContractState) { + self.switch._off(); + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + SwitchableEvent: switchable_component::Event, + } + + #[abi(embed_v0)] + impl SwitchCollisionContract of super::ISwitchCollision { + fn set(ref self: ContractState, value: bool) { + self.switchable_value.write(value); + } + + fn get(ref self: ContractState) -> bool { + self.switchable_value.read() + } + } +} + +#[cfg(test)] +mod switch_collision_tests { + use components::switchable::switchable_component::SwitchableInternalTrait; + use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait}; + use super::{ + SwitchCollisionContract, ISwitchCollisionDispatcher, ISwitchCollisionDispatcherTrait + }; + use starknet::storage::StorageMemberAccessTrait; + use starknet::SyscallResultTrait; + use starknet::syscalls::deploy_syscall; + + fn deploy() -> (ISwitchCollisionDispatcher, ISwitchableDispatcher) { + let (contract_address, _) = deploy_syscall( + SwitchCollisionContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + + ( + ISwitchCollisionDispatcher { contract_address }, + ISwitchableDispatcher { contract_address }, + ) + } + + #[test] + // ANCHOR: collision + fn test_collision() { + let (mut contract, mut contract_iswitch) = deploy(); + + assert_eq!(contract.get(), false); + assert_eq!(contract_iswitch.is_on(), false); + + contract_iswitch.switch(); + assert_eq!(contract_iswitch.is_on(), true); + assert_eq!(contract.get(), true); + + // `collision` between component storage 'value' and contract storage 'value' + assert_eq!(contract.get(), contract_iswitch.is_on()); + + contract.set(false); + assert_eq!(contract.get(), contract_iswitch.is_on()); + } +// ANCHOR_END: collision +} diff --git a/listings/applications/components/src/ownable.cairo b/listings/applications/components/src/ownable.cairo index cca82ca5..68919dd7 100644 --- a/listings/applications/components/src/ownable.cairo +++ b/listings/applications/components/src/ownable.cairo @@ -1,3 +1,4 @@ +// ANCHOR: component use starknet::ContractAddress; #[starknet::interface] @@ -15,8 +16,8 @@ pub mod Errors { #[starknet::component] pub mod ownable_component { - use starknet::{ContractAddress, get_caller_address}; use super::Errors; + use starknet::{ContractAddress, get_caller_address}; use core::num::traits::Zero; #[storage] @@ -24,19 +25,19 @@ pub mod ownable_component { ownable_owner: ContractAddress, } - #[derive(Drop, starknet::Event)] - struct OwnershipTransferredEvent { - previous: ContractAddress, - new: ContractAddress + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct OwnershipTransferredEvent { + pub previous: ContractAddress, + pub new: ContractAddress } - #[derive(Drop, starknet::Event)] - struct OwnershipRenouncedEvent { - previous: ContractAddress + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct OwnershipRenouncedEvent { + pub previous: ContractAddress } #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { OwnershipTransferredEvent: OwnershipTransferredEvent, OwnershipRenouncedEvent: OwnershipRenouncedEvent @@ -93,3 +94,140 @@ pub mod ownable_component { } } } +// ANCHOR_END: component + +// ANCHOR: contract +#[starknet::contract] +pub mod OwnedContract { + use super::{IOwnable, ownable_component, ownable_component::OwnableInternalTrait}; + + component!(path: ownable_component, storage: ownable, event: OwnableEvent); + + #[abi(embed_v0)] + impl OwnableImpl = ownable_component::Ownable; + + #[storage] + struct Storage { + #[substorage(v0)] + ownable: ownable_component::Storage, + } + + #[constructor] + fn constructor(ref self: ContractState) { + self.ownable._init(starknet::get_caller_address()); + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + OwnableEvent: ownable_component::Event, + } +} +// ANCHOR_END: contract + +#[cfg(test)] +mod test { + use super::OwnedContract; + use super::ownable_component::{Event, OwnershipRenouncedEvent, OwnershipTransferredEvent}; + use super::{IOwnableDispatcher, IOwnableDispatcherTrait}; + use super::Errors; + use starknet::ContractAddress; + use starknet::{syscalls::deploy_syscall, SyscallResultTrait, contract_address_const}; + use starknet::testing::{set_caller_address, set_contract_address}; + use core::traits::TryInto; + use core::num::traits::Zero; + + fn deploy() -> (IOwnableDispatcher, ContractAddress) { + let (contract_address, _) = deploy_syscall( + OwnedContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + + (IOwnableDispatcher { contract_address }, contract_address) + } + + #[test] + fn test_initial_state() { + let owner = contract_address_const::<'owner'>(); + set_contract_address(owner); + let (ownable, _) = deploy(); + + assert_eq!(ownable.owner(), owner); + } + + #[test] + fn test_transfer_ownership() { + let contract_address = contract_address_const::<'owner'>(); + set_contract_address(contract_address); + let (ownable, address) = deploy(); + let new_owner = contract_address_const::<'new_owner'>(); + + ownable.transfer_ownership(new_owner); + assert_eq!(ownable.owner(), new_owner); + assert_eq!( + starknet::testing::pop_log(address), + Option::Some( + OwnedContract::Event::OwnableEvent( + OwnershipTransferredEvent { previous: contract_address, new: new_owner }.into() + ) + ) + ); + } + + #[test] + #[should_panic] + fn test_transfer_ownership_not_owner() { + set_contract_address(contract_address_const::<'initial'>()); + let (ownable, _) = deploy(); + + set_contract_address(contract_address_const::<'not_owner'>()); + ownable.transfer_ownership(contract_address_const::<'new_owner'>()); + } + + #[test] + #[should_panic] + fn test_transfer_ownership_zero_error() { + set_contract_address(contract_address_const::<'initial'>()); + let (ownable, _) = deploy(); + + ownable.transfer_ownership(Zero::zero()); + } + + #[test] + fn test_renounce_ownership() { + let contract_address = contract_address_const::<'owner'>(); + set_contract_address(contract_address); + let (ownable, address) = deploy(); + + ownable.renounce_ownership(); + assert_eq!(ownable.owner(), Zero::zero()); + assert_eq!( + starknet::testing::pop_log(address), + Option::Some( + OwnedContract::Event::OwnableEvent( + OwnershipRenouncedEvent { previous: contract_address }.into() + ) + ) + ); + } + + #[test] + #[should_panic] + fn test_renounce_ownership_not_owner() { + set_contract_address(contract_address_const::<'owner'>()); + let (ownable, _) = deploy(); + + set_contract_address(contract_address_const::<'not_owner'>()); + ownable.renounce_ownership(); + } + + #[test] + #[should_panic] + fn test_renounce_ownership_previous_owner() { + set_contract_address(contract_address_const::<'owner'>()); + let (ownable, _) = deploy(); + + ownable.renounce_ownership(); + ownable.transfer_ownership(contract_address_const::<'new_owner'>()); + } +} diff --git a/listings/applications/components/src/switchable.cairo b/listings/applications/components/src/switchable.cairo index b2ce28ea..e58096df 100644 --- a/listings/applications/components/src/switchable.cairo +++ b/listings/applications/components/src/switchable.cairo @@ -14,11 +14,11 @@ pub mod switchable_component { switchable_value: bool, } - #[derive(Drop, starknet::Event)] - struct SwitchEvent {} + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub struct SwitchEvent {} #[event] - #[derive(Drop, starknet::Event)] + #[derive(Drop, Debug, PartialEq, starknet::Event)] pub enum Event { SwitchEvent: SwitchEvent, } @@ -48,4 +48,84 @@ pub mod switchable_component { } // ANCHOR_END: component +// ANCHOR: contract +#[starknet::contract] +pub mod SwitchContract { + use super::switchable_component; + + component!(path: switchable_component, storage: switch, event: SwitchableEvent); + + #[abi(embed_v0)] + impl SwitchableImpl = switchable_component::Switchable; + + #[storage] + struct Storage { + #[substorage(v0)] + switch: switchable_component::Storage, + } + + #[event] + #[derive(Drop, Debug, PartialEq, starknet::Event)] + pub enum Event { + SwitchableEvent: switchable_component::Event, + } + + // You can optionally use the internal implementation of the component as well + impl SwitchableInternalImpl = switchable_component::SwitchableInternalImpl; + + #[constructor] + fn constructor(ref self: ContractState) { + // Internal function call + self.switch._off(); + } +} +// ANCHOR_END: contract + +// ANCHOR: tests +#[cfg(test)] +mod test { + use super::SwitchContract; // Used as a mock contract + use super::switchable_component::{Event, SwitchEvent}; + use super::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; + use starknet::{syscalls::deploy_syscall, contract_address_const, ContractAddress}; + use starknet::SyscallResultTrait; + + fn deploy() -> (ISwitchableDispatcher, ContractAddress) { + let (address, _) = deploy_syscall( + SwitchContract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false + ) + .unwrap_syscall(); + (ISwitchableDispatcher { contract_address: address }, address) + } + + #[test] + fn test_constructor() { + let (switchable, _) = deploy(); + assert_eq!(switchable.is_on(), false); + } + + #[test] + fn test_switch() { + let (switchable, contract_address) = deploy(); + switchable.switch(); + assert_eq!(switchable.is_on(), true); + assert_eq!( + starknet::testing::pop_log(contract_address), + Option::Some(SwitchContract::Event::SwitchableEvent(SwitchEvent {}.into())) + ); + } + + #[test] + fn test_multiple_switches() { + let (switchable, _) = deploy(); + switchable.switch(); + assert_eq!(switchable.is_on(), true); + switchable.switch(); + assert_eq!(switchable.is_on(), false); + switchable.switch(); + assert_eq!(switchable.is_on(), true); + } +} +// ANCHOR_END: tests + diff --git a/scripts/cairo_programs_verifier.sh b/scripts/cairo_programs_verifier.sh index b55cdd5b..394e08e9 100755 --- a/scripts/cairo_programs_verifier.sh +++ b/scripts/cairo_programs_verifier.sh @@ -97,7 +97,7 @@ else if [ -z "$modified_listings" ]; then echo -e "\n${GREEN}No new changes detected${NC}" else - echo -e "\n${GREEN}All $listings_count builds were completed successfully${NC}" + echo -e "\n${GREEN}All builds were completed successfully${NC}" fi rm "$error_file" exit 0 diff --git a/src/components/collisions.md b/src/components/collisions.md index 5f8266be..98bf9448 100644 --- a/src/components/collisions.md +++ b/src/components/collisions.md @@ -16,17 +16,17 @@ Here's an example of a collision on the `switchable_value` storage variable of t Interface: ```rust -{{#include ../../listings/applications/components/src/contracts/switch_collision.cairo:interface}} +{{#include ../../listings/applications/components/src/others/switch_collision.cairo:interface}} ``` -Here's the storage of the contract (you can expand the code snippet to see the full contract): +Here's the storage of the contract (you can expand the code snippet to see the full contract and tests): ```rust -{{#rustdoc_include ../../listings/applications/components/src/contracts/switch_collision.cairo:storage}} +{{#rustdoc_include ../../listings/applications/components/src/others/switch_collision.cairo:storage}} ``` Both the contract and the component have a `switchable_value` storage variable, so they collide: ```rust -{{#rustdoc_include ../../listings/applications/components/src/contracts/tests/switch_collision_tests.cairo:collision}} +{{#include ../../listings/applications/components/src/others/switch_collision.cairo:collision}} ``` diff --git a/src/components/dependencies.md b/src/components/dependencies.md index 08f20ec2..31431aca 100644 --- a/src/components/dependencies.md +++ b/src/components/dependencies.md @@ -5,7 +5,7 @@ A component with a dependency on a trait `T` can be used in a contract as long a We will use a new `Countable` component as an example: ```rust -{{#include ../../listings/applications/components/src/countable.cairo}} +{{#rustdoc_include ../../listings/applications/components/src/countable.cairo:component}} ``` We want to add a way to enable or disable the counter, in a way that calling `increment` on a disabled counter will not increment it. diff --git a/src/components/how_to.md b/src/components/how_to.md index 30f8cbea..819954f1 100644 --- a/src/components/how_to.md +++ b/src/components/how_to.md @@ -42,7 +42,18 @@ Now that we have a component, we can use it in a contract. The following contract incorporates the `Switchable` component: ```rust -{{#rustdoc_include ../../listings/applications/components/src/contracts/switch.cairo:contract}} +{{#include ../../listings/applications/components/src/switchable.cairo:contract}} +``` + +## How to test a component + +In order to effectively test a component, you need to test it in the context of a contract. +A common practice is to declare a `Mock` contract that has the only purpose of testing the component. + +To test the `Switchable` component, we can use the previous `SwitchableContract`: + +```rust +{{#include ../../listings/applications/components/src/switchable.cairo:tests}} ``` ## Deep dive into components diff --git a/src/components/ownable.md b/src/components/ownable.md index 003ccb9a..20d8f313 100644 --- a/src/components/ownable.md +++ b/src/components/ownable.md @@ -5,11 +5,11 @@ The following `Ownable` component is a simple component that allows the contract It can also be used to renounce ownership of a contract, meaning that no one will be able to satisfy the `_assert_is_owner` function. ```rust -{{#include ../../listings/applications/components/src/ownable.cairo}} +{{#include ../../listings/applications/components/src/ownable.cairo:component}} ``` A mock contract that uses the `Ownable` component: ```rust -{{#rustdoc_include ../../listings/applications/components/src/contracts/owned.cairo:contract}} +{{#rustdoc_include ../../listings/applications/components/src/ownable.cairo:contract}} ``` From 630092fa8084a89089a38c8ae2ffc1d2f8ba9e97 Mon Sep 17 00:00:00 2001 From: Jemiiah <160767568+Jemiiah@users.noreply.github.com> Date: Mon, 1 Jul 2024 08:21:01 +0100 Subject: [PATCH 23/36] doc: SNIP-6 implementation (#200) * test: SNIP-6 implementation * docs: errors recheck * test: SNIP-6 implementation * docs: errors recheck * feat: add simple account example * feat/fix: revisions on #200 * feat:implement SRC5 * feat: implementation with oz * fix: oz impl src5 for account --------- Co-authored-by: Oluwaseun Jeremiah Co-authored-by: julio4 <30329843+julio4@users.noreply.github.com> --- Scarb.lock | 5 +- .../simple_account/.gitignore | 1 + .../simple_account/Scarb.lock | 6 ++ .../simple_account/Scarb.toml | 14 +++ .../simple_account/src/lib.cairo | 4 + .../simple_account/src/simple_account.cairo | 90 +++++++++++++++++++ .../simple_account/src/tests.cairo | 3 + src/SUMMARY.md | 6 +- .../account_abstraction/account_contract.md | 73 +++++++++++++++ .../account_abstraction/index.md | 13 +++ 10 files changed, 212 insertions(+), 3 deletions(-) create mode 100644 listings/advanced-concepts/simple_account/.gitignore create mode 100644 listings/advanced-concepts/simple_account/Scarb.lock create mode 100644 listings/advanced-concepts/simple_account/Scarb.toml create mode 100644 listings/advanced-concepts/simple_account/src/lib.cairo create mode 100644 listings/advanced-concepts/simple_account/src/simple_account.cairo create mode 100644 listings/advanced-concepts/simple_account/src/tests.cairo create mode 100644 src/advanced-concepts/account_abstraction/account_contract.md create mode 100644 src/advanced-concepts/account_abstraction/index.md diff --git a/Scarb.lock b/Scarb.lock index a055e8a9..96d58087 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -125,8 +125,11 @@ name = "scarb" version = "0.1.0" [[package]] -name = "simple_storage" +name = "simple_account" version = "0.1.0" +dependencies = [ + "openzeppelin", +] [[package]] name = "simple_vault" diff --git a/listings/advanced-concepts/simple_account/.gitignore b/listings/advanced-concepts/simple_account/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/advanced-concepts/simple_account/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/advanced-concepts/simple_account/Scarb.lock b/listings/advanced-concepts/simple_account/Scarb.lock new file mode 100644 index 00000000..f287d8c3 --- /dev/null +++ b/listings/advanced-concepts/simple_account/Scarb.lock @@ -0,0 +1,6 @@ +# Code generated by scarb DO NOT EDIT. +version = 1 + +[[package]] +name = "ecdsa_verification" +version = "0.1.0" diff --git a/listings/advanced-concepts/simple_account/Scarb.toml b/listings/advanced-concepts/simple_account/Scarb.toml new file mode 100644 index 00000000..f6fbac41 --- /dev/null +++ b/listings/advanced-concepts/simple_account/Scarb.toml @@ -0,0 +1,14 @@ +[package] +name = "simple_account" +version.workspace = true +edition = '2023_11' + + +[dependencies] +starknet.workspace = true +openzeppelin.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] diff --git a/listings/advanced-concepts/simple_account/src/lib.cairo b/listings/advanced-concepts/simple_account/src/lib.cairo new file mode 100644 index 00000000..011ae9e3 --- /dev/null +++ b/listings/advanced-concepts/simple_account/src/lib.cairo @@ -0,0 +1,4 @@ +mod simple_account; + +#[cfg(test)] +mod tests; diff --git a/listings/advanced-concepts/simple_account/src/simple_account.cairo b/listings/advanced-concepts/simple_account/src/simple_account.cairo new file mode 100644 index 00000000..6de8b4ae --- /dev/null +++ b/listings/advanced-concepts/simple_account/src/simple_account.cairo @@ -0,0 +1,90 @@ +use starknet::account::Call; + +#[starknet::interface] +trait ISRC6 { + fn execute_calls(self: @TContractState, calls: Array) -> Array>; + fn validate_calls(self: @TContractState, calls: Array) -> felt252; + fn is_valid_signature( + self: @TContractState, hash: felt252, signature: Array + ) -> felt252; +} + +#[starknet::contract] +mod simpleAccount { + use super::ISRC6; + use starknet::account::Call; + use core::num::traits::Zero; + use core::ecdsa::check_ecdsa_signature; + + // Implement SRC5 with openzeppelin + use openzeppelin::account::interface; + use openzeppelin::introspection::src5::SRC5Component; + component!(path: SRC5Component, storage: src5, event: SRC5Event); + + #[abi(embed_v0)] + impl SRC5Impl = SRC5Component::SRC5Impl; + impl SRC5InternalImpl = SRC5Component::InternalImpl; + + #[storage] + struct Storage { + #[substorage(v0)] + src5: SRC5Component::Storage, + public_key: felt252 + } + + #[constructor] + fn constructor(ref self: ContractState, public_key: felt252) { + self.src5.register_interface(interface::ISRC6_ID); + self.public_key.write(public_key); + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event { + #[flat] + SRC5Event: SRC5Component::Event + } + + #[abi(embed_v0)] + impl SRC6 of ISRC6 { + fn execute_calls(self: @ContractState, calls: Array) -> Array> { + assert(starknet::get_caller_address().is_zero(), 'Not Starknet Protocol'); + let Call { to, selector, calldata } = calls.at(0); + let res = starknet::syscalls::call_contract_syscall(*to, *selector, *calldata).unwrap(); + array![res] + } + + fn validate_calls(self: @ContractState, calls: Array) -> felt252 { + assert(starknet::get_caller_address().is_zero(), 'Not Starknet Protocol'); + let tx_info = starknet::get_tx_info().unbox(); + let tx_hash = tx_info.transaction_hash; + let signature = tx_info.signature; + if self._is_valid_signature(tx_hash, signature) { + starknet::VALIDATED + } else { + 0 + } + } + + fn is_valid_signature( + self: @ContractState, hash: felt252, signature: Array + ) -> felt252 { + if self._is_valid_signature(hash, signature.span()) { + starknet::VALIDATED + } else { + 0 + } + } + } + + #[generate_trait] + impl SignatureVerificationImpl of SignatureVerification { + fn _is_valid_signature( + self: @ContractState, hash: felt252, signature: Span + ) -> bool { + check_ecdsa_signature( + hash, self.public_key.read(), *signature.at(0_u32), *signature.at(1_u32) + ) + } + } +} diff --git a/listings/advanced-concepts/simple_account/src/tests.cairo b/listings/advanced-concepts/simple_account/src/tests.cairo new file mode 100644 index 00000000..aefd6eb5 --- /dev/null +++ b/listings/advanced-concepts/simple_account/src/tests.cairo @@ -0,0 +1,3 @@ +#[cfg(test)] +mod tests { // TODO +} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 07551165..ae79c8d8 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -72,10 +72,12 @@ Summary - [Struct as mapping key](./advanced-concepts/struct-mapping-key.md) - [Hashing](./advanced-concepts/hashing.md) - + - [Optimisations](./advanced-concepts/optimisations/optimisations.md) - [Storage Optimisations](./advanced-concepts/optimisations/store_using_packing.md) +- [Account Abstraction](./advanced-concepts/account_abstraction/index.md) + - [Account Contract](./advanced-concepts/account_abstraction/account_contract.md) - [List](./advanced-concepts/list.md) +- [Library Calls](./advanced-concepts/library_calls.md) - [Plugins](./advanced-concepts/plugins.md) - [Signature Verification](./advanced-concepts/signature_verification.md) -- [Library Calls](./advanced-concepts/library_calls.md) diff --git a/src/advanced-concepts/account_abstraction/account_contract.md b/src/advanced-concepts/account_abstraction/account_contract.md new file mode 100644 index 00000000..1cd8dde9 --- /dev/null +++ b/src/advanced-concepts/account_abstraction/account_contract.md @@ -0,0 +1,73 @@ +# Account Contract + +A smart contract must follow the Standard Account Interface specification defined in the [SNIP-6](https://github.com/starknet-io/SNIPs/blob/main/SNIPS/snip-6.md). +In practice, this means that the contract must implement the `SRC6` and `SRC5` interfaces to be considered an account contract. + +## SNIP-6: SRC6 + SRC5 + +```rust +/// @title Represents a call to a target contract +/// @param to The target contract address +/// @param selector The target function selector +/// @param calldata The serialized function parameters +struct Call { + to: ContractAddress, + selector: felt252, + calldata: Array +} +``` + +The `Call` struct is used to represent a call to a function (`selector`) in a target contract (`to`) with parameters (`calldata`). It is available under the `starknet::account` module. + +```rust +/// @title SRC-6 Standard Account +trait ISRC6 { + /// @notice Execute a transaction through the account + /// @param calls The list of calls to execute + /// @return The list of each call's serialized return value + fn __execute__(calls: Array) -> Array>; + + /// @notice Assert whether the transaction is valid to be executed + /// @param calls The list of calls to execute + /// @return The string 'VALID' represented as felt when is valid + fn __validate__(calls: Array) -> felt252; + + /// @notice Assert whether a given signature for a given hash is valid + /// @param hash The hash of the data + /// @param signature The signature to validate + /// @return The string 'VALID' represented as felt when the signature is valid + fn is_valid_signature(hash: felt252, signature: Array) -> felt252; +} +``` + +A transaction can be represented as a list of calls `Array` to other contracts, with atleast one call. + +- `__execute__`: Executes a transaction after the validation phase. Returns an array of the serialized return of value (`Span`) of each call. + +- `__validate__`: Validates a transaction by verifying some predefined rules, such as the signature of the transaction. Returns the `VALID` short string (as a felt252) if the transaction is valid. + +- `is_valid_signature`: Verify that a given signature is valid. This is mainly used by applications for authentication purposes. + +Both `__execute__` and `__validate__` functions are exclusively called by the Starknet protocol. + + + +```rust +/// @title SRC-5 Standard Interface Detection +trait ISRC5 { + /// @notice Query if a contract implements an interface + /// @param interface_id The interface identifier, as specified in SRC-5 + /// @return `true` if the contract implements `interface_id`, `false` otherwise + fn supports_interface(interface_id: felt252) -> bool; +} +``` + +The interface identifiers of both `SRC5` and `SRC6` must be published with `supports_interface`. + +## Minimal account contract Executing Transactions + +In this example, we will implement a minimal account contract that can validate and execute transactions. + +```rust +{{#rustdoc_include ../../../listings/advanced-concepts/simple_account/src/simple_account.cairo}} +``` \ No newline at end of file diff --git a/src/advanced-concepts/account_abstraction/index.md b/src/advanced-concepts/account_abstraction/index.md new file mode 100644 index 00000000..f345ab99 --- /dev/null +++ b/src/advanced-concepts/account_abstraction/index.md @@ -0,0 +1,13 @@ +# Account Abstraction + +An account is an unique entity that can send transactions, users usually use wallets to manage their accounts. + +Historically, in Ethereum, all accounts were Externally Owned Accounts (_EOA_) and were controlled by private keys. This is a simple and secure way to manage accounts, but it has limitations as the account logic is hardcoded in the protocol. + +Account Abstraction (_AA_) is the concept behind abstracting parts of the account logic to allow for a more flexible account system. +This replaces EOA with Account Contracts, which are smart contracts that implement the account logic. This opens up a lot of possibilities that can significantly improve the user experience when dealing with accounts. + +On Starknet, Account Abstraction is natively supported, and all accounts are Account Contracts. + +In this section we will how to implement an Account. + From 098f403d12440f7ec6d9f0802c33354b20b1251f Mon Sep 17 00:00:00 2001 From: Adetula Olamide <67712894+LamsyA@users.noreply.github.com> Date: Fri, 19 Jul 2024 09:14:03 +0100 Subject: [PATCH 24/36] simple_vault test implementation (#220) --- listings/applications/simple_vault/Scarb.toml | 1 + .../applications/simple_vault/src/lib.cairo | 1 + .../simple_vault/src/simple_vault.cairo | 162 ++++++++++++++++-- 3 files changed, 147 insertions(+), 17 deletions(-) diff --git a/listings/applications/simple_vault/Scarb.toml b/listings/applications/simple_vault/Scarb.toml index 346b42bb..561214d3 100644 --- a/listings/applications/simple_vault/Scarb.toml +++ b/listings/applications/simple_vault/Scarb.toml @@ -5,6 +5,7 @@ edition = '2023_11' [dependencies] starknet.workspace = true +erc20 = { path = "../erc20" } [scripts] test.workspace = true diff --git a/listings/applications/simple_vault/src/lib.cairo b/listings/applications/simple_vault/src/lib.cairo index b08098a0..b5492840 100644 --- a/listings/applications/simple_vault/src/lib.cairo +++ b/listings/applications/simple_vault/src/lib.cairo @@ -1,4 +1,5 @@ mod simple_vault; + #[cfg(test)] mod tests; diff --git a/listings/applications/simple_vault/src/simple_vault.cairo b/listings/applications/simple_vault/src/simple_vault.cairo index 22cd61b9..e8c0191b 100644 --- a/listings/applications/simple_vault/src/simple_vault.cairo +++ b/listings/applications/simple_vault/src/simple_vault.cairo @@ -4,23 +4,34 @@ use starknet::ContractAddress; // we need to have the interface of the remote ERC20 contract defined to import the Dispatcher. #[starknet::interface] pub trait IERC20 { - fn name(self: @TContractState) -> felt252; - fn symbol(self: @TContractState) -> felt252; - fn decimals(self: @TContractState) -> u8; - fn total_supply(self: @TContractState) -> u256; - fn balance_of(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 get_name(self: @TContractState) -> felt252; + fn get_symbol(self: @TContractState) -> felt252; + fn get_decimals(self: @TContractState) -> u8; + fn get_total_supply(self: @TContractState) -> felt252; + fn balance_of(self: @TContractState, account: ContractAddress) -> felt252; + fn allowance( + self: @TContractState, owner: ContractAddress, spender: ContractAddress + ) -> felt252; + fn transfer(ref self: TContractState, recipient: ContractAddress, amount: felt252); fn transfer_from( - ref self: TContractState, sender: ContractAddress, recipient: ContractAddress, amount: u256 - ) -> bool; - fn approve(ref self: TContractState, spender: ContractAddress, amount: u256) -> bool; + ref self: TContractState, + sender: ContractAddress, + recipient: ContractAddress, + amount: felt252 + ); + fn approve(ref self: TContractState, spender: ContractAddress, amount: felt252); + fn increase_allowance(ref self: TContractState, spender: ContractAddress, added_value: felt252); + fn decrease_allowance( + ref self: TContractState, spender: ContractAddress, subtracted_value: felt252 + ); } #[starknet::interface] pub trait ISimpleVault { fn deposit(ref self: TContractState, amount: u256); fn withdraw(ref self: TContractState, shares: u256); + fn user_balance_of(ref self: TContractState, account: ContractAddress) -> u256; + fn contract_total_supply(ref self: TContractState) -> u256; } #[starknet::contract] @@ -51,11 +62,22 @@ pub mod SimpleVault { self.total_supply.write(self.total_supply.read() - shares); self.balance_of.write(from, self.balance_of.read(from) - shares); } + } #[abi(embed_v0)] impl SimpleVault of super::ISimpleVault { - fn deposit(ref self: ContractState, amount: u256) { + + fn user_balance_of(ref self: ContractState, account: ContractAddress) -> u256 { + self.balance_of.read(account) + } + + fn contract_total_supply(ref self: ContractState) -> u256 { + self.total_supply.read() + } + + + fn deposit(ref self: ContractState, amount: u256){ // a = amount // B = balance of token before deposit // T = total supply @@ -71,12 +93,15 @@ pub mod SimpleVault { if self.total_supply.read() == 0 { shares = amount; } else { - let balance = self.token.read().balance_of(this); + let balance: u256 = self.token.read().balance_of(this).try_into() + .unwrap(); shares = (amount * self.total_supply.read()) / balance; } - - PrivateFunctions::_mint(ref self, caller, shares); - self.token.read().transfer_from(caller, this, amount); + + PrivateFunctions::_mint(ref self, caller, shares); + + let amount_felt252: felt252 = amount.low.into(); + self.token.read().transfer_from(caller, this, amount_felt252); } fn withdraw(ref self: ContractState, shares: u256) { @@ -91,11 +116,114 @@ pub mod SimpleVault { let caller = get_caller_address(); let this = get_contract_address(); - let balance = self.token.read().balance_of(this); + let balance = self.user_balance_of(this); let amount = (shares * balance) / self.total_supply.read(); PrivateFunctions::_burn(ref self, caller, shares); - self.token.read().transfer(caller, amount); + let amount_felt252: felt252 = amount.low.into(); + self.token.read().transfer(caller, amount_felt252); } } } +// ANCHOR_END: simple_vault + +#[cfg(test)] +mod tests { + use core::traits::Into; + use super::{ + SimpleVault, ISimpleVaultDispatcher, ISimpleVaultDispatcherTrait, IERC20Dispatcher, + IERC20DispatcherTrait + }; + + // use erc20::token::IERC20; + use erc20::token::{IERC20DispatcherTrait as IERC20DispatcherTrait_token, + IERC20Dispatcher as IERC20Dispatcher_token + }; + + use core::num::traits::Zero; + + use starknet::testing::{set_contract_address, set_account_contract_address}; + use starknet::{ + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, + contract_address_const + }; + + const token_name: felt252 = 'myToken'; + const decimals: u8 = 18; + const initial_supply: felt252 = 100000; + const symbols: felt252 = 'mtk'; + + fn deploy() -> (ISimpleVaultDispatcher, ContractAddress, IERC20Dispatcher_token) { + let _token_address: ContractAddress = contract_address_const::<'token_address'>(); + let caller = contract_address_const::<'caller'>(); + + let (token_contract_address, _) = deploy_syscall( + erc20::token::erc20::TEST_CLASS_HASH.try_into().unwrap(), + caller.into(), + array![caller.into(), 'myToken', '8', '1000'.into(), 'MYT'].span(), + false + ) + .unwrap_syscall(); + + let (contract_address, _) = deploy_syscall( + SimpleVault::TEST_CLASS_HASH.try_into().unwrap(), + 0, + array![token_contract_address.into()].span(), + false + ) + .unwrap_syscall(); + ( + ISimpleVaultDispatcher { contract_address }, + contract_address, + IERC20Dispatcher_token { contract_address: token_contract_address } + ) + } + + #[test] + fn test_deposit() { + let caller = contract_address_const::<'caller'>(); + let (dispatcher, vault_address, token_dispatcher) = deploy(); + + // Approve the vault to transfer tokens on behalf of the caller + let amount: felt252 = 10.into(); + token_dispatcher.approve(vault_address.into(), amount); + set_contract_address(caller); + + // Deposit tokens into the vault + let amount: u256 = 10.into(); + let _deposit = dispatcher.deposit(amount); + println!("deposit :{:?}", _deposit); + + // Check balances and total supply + let balance_of_caller = dispatcher.user_balance_of(caller); + let total_supply = dispatcher.contract_total_supply(); + + assert(balance_of_caller == amount, 'Deposit failed'); + assert(total_supply == amount, 'total supply mismatch'); + + } + + #[test] + fn test_deposit_withdraw() { + let caller = contract_address_const::<'caller'>(); + let (dispatcher, vault_address, token_dispatcher) = deploy(); + + // Approve the vault to transfer tokens on behalf of the caller + let amount: felt252 = 10.into(); + token_dispatcher.approve(vault_address.into(), amount); + set_contract_address(caller); + set_account_contract_address(vault_address); + + // Deposit tokens into the vault + let amount: u256 = 10.into(); + dispatcher.deposit(amount); + dispatcher.withdraw(amount); + + // Check balances of user in the vault after withdraw + let balance_of_caller = dispatcher.user_balance_of(caller); + + assert(balance_of_caller == 0.into(), 'withdraw failed'); + } + + +} \ No newline at end of file From b442f2daaa347cd261f138183f95d9fcc5fb5da7 Mon Sep 17 00:00:00 2001 From: Orlando Sanchez Date: Fri, 19 Jul 2024 04:23:09 -0400 Subject: [PATCH 25/36] Update es.po (#231) Some updates and corrections. --- po/es.po | 86 +++++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 60 insertions(+), 26 deletions(-) diff --git a/po/es.po b/po/es.po index baa154ae..51cdcf9b 100644 --- a/po/es.po +++ b/po/es.po @@ -87,11 +87,11 @@ msgstr "Llamar a otros contratos" #: src/SUMMARY.md:26 msgid "Factory pattern" -msgstr "Patrón de la Factory" +msgstr "Patrón Factory" #: src/SUMMARY.md:27 msgid "Testing contracts" -msgstr "Contratos de Testing" +msgstr "Testing de Contratos" #: src/SUMMARY.md:28 msgid "Cairo cheatsheet" @@ -220,7 +220,7 @@ msgstr "" #: src/starknet-by-example.md:5 msgid "" "Starknet is a permissionless Validity-Rollup that supports general " -"computation. It is currently used as an Ethereum layer-2. Starknet use the " +"computation. It is currently used as an Ethereum layer-2. Starknet uses the " "STARK cryptographic proof system to ensure high safety and scalability." msgstr "" "Starknet es un Validity-Rollup sin permiso que admite el cálculo general. " @@ -234,26 +234,12 @@ msgid "" "Turing-complete programming language designed to write provable programs, " "abstracting the zk-STARK proof system away from the programmer." msgstr "" -"Los smart contracts de Starknet están escritos en el idioma de Cairo. Cairo " -"es un lenguaje de programación completo de Turing diseñado para escribir " +"Los smart contracts de Starknet están escritos en el lenguaje Cairo. Cairo " +"es un lenguaje de programación Turing completo diseñado para escribir " "programas demostrables, abstrayendo el sistema de prueba zk-STARK del " "programador." #: src/starknet-by-example.md:9 -msgid "The current version of this book use:" -msgstr "La versión actual de este libro usa:" - -#: src/starknet-by-example.md:10 -msgid "" -"```\n" -"scarb 2.3.1\n" -"```" -msgstr "" -"```\n" -"scarb 2.3.1\n" -"```" - -#: src/starknet-by-example.md:14 msgid "" "> ⚠️ The examples have not been audited and are not intended for production " "use.\n" @@ -265,11 +251,11 @@ msgstr "" "> Los autores no se hacen responsables de los daños causados por el uso del " "código proporcionado en este libro." -#: src/starknet-by-example.md:17 +#: src/starknet-by-example.md:12 msgid "## For whom is this for?" -msgstr "## ¿Para quién es esto?" +msgstr "## ¿A quién va dirigido?" -#: src/starknet-by-example.md:19 +#: src/starknet-by-example.md:14 msgid "" "Starknet By Example is for anyone who wants to quickly learn how to write " "smart contracts on Starknet using Cairo with some technical background in " @@ -279,7 +265,7 @@ msgstr "" "escribir contratos inteligentes en Starknet usando Cairo con cierta " "experiencia técnica en programación y blockchain." -#: src/starknet-by-example.md:21 +#: src/starknet-by-example.md:16 msgid "" "The first chapters will give you a basic understanding of the Cairo " "programming language and how to write, deploy and use smart contracts on " @@ -292,11 +278,41 @@ msgstr "" "inteligentes en Starknet. Los capítulos posteriores cubrirán temas más " "avanzados y le mostrarán cómo escribir contratos inteligentes más complejos." -#: src/starknet-by-example.md:24 +#: src/starknet-by-example.md:19 +msgid "## How to use this book?" +msgstr "## ¿Como usar este libro?" + +#: src/starknet-by-example.md:21 +msgid "" +"Each chapter is a standalone example that demonstrates a specific feature " +"or common use case of smart contracts on Starknet. If you are new to Starknet, " +"it is recommended to read the chapters in order." +msgstr "" +"Cada capítulo en un ejemplo independiente que muestra una característica " +"específica o un caso de uso común de un contrato inteligente en Starknet. " +"Si eres nuevo en Starknet, es recomendable que leas los capítulos en orden." + +#: src/starknet-by-example.md:23 +msgid "" +"Most examples contain interfaces and tests that are hidden by default. " +"You can hover over the code blocks and click on the \"Show hidden lines\" " +"(eyes icon) to see the hidden code.\n" +msgstr "" +"La mayoria de los ejemplos contienen interfaces y pruebas que están ocultas por defecto " +"Puedes colocar el cursor sobre los bloques de codígo y darle click en \"Mostrar lineas ocultas\" " +"(icono de ojos) para ver el codígo oculto" + +#: src/starknet-by-example.md:25 +msgid "" +"You can run each example online by using the [Starknet Remix Plugin](https://remix.ethereum.org/?#activate=Starknet)." +msgstr "" +"Puedes ejecutar cada uno de los ejemplos en línea usando [El plugin de Starknet en Remix ](https://remix.ethereum.org/?#activate=Starknet)." + +#: src/starknet-by-example.md:27 msgid "## Further reading" msgstr "## Otras lecturas" -#: src/starknet-by-example.md:26 +#: src/starknet-by-example.md:29 msgid "" "If you want to learn more about the Cairo programming language, you can read " "the [Cairo Book](https://book.cairo-lang.org).\n" @@ -309,7 +325,7 @@ msgstr "" "más información sobre Starknet, puede leer la [Documentación de Starknet]" "(https://docs.starknet.io/) y el [Starknet Book](https://book.starknet.io)." -#: src/starknet-by-example.md:29 +#: src/starknet-by-example.md:32 msgid "" "For more resources, check [Awesome Starknet](https://github.com/keep-" "starknet-strange/awesome-starknet)." @@ -317,6 +333,24 @@ msgstr "" "Para obtener más recursos, consulte [Awesome Starknet](https://github.com/" "keep-starknet-strange/awesome-starknet)." +#: src/starknet-by-example.md:34 +msgid "## Versions" +msgstr "## Versiones" + +#: src/starknet-by-example.md:36 +msgid "The current version of this book uses:" +msgstr "La versión actual de este libro usa:" + +#: src/starknet-by-example.md:38 +msgid "" +"```\n" +"scarb 2.6.3\n" +"```" +msgstr "" +"```\n" +"edition = '2023_11'\n" +"```" + #: src/starknet-by-example.md:31 src/getting-started/basics/storage.md:34 #: src/getting-started/basics/constructor.md:27 src/getting-started/basics/variables.md:126 #: src/getting-started/basics/visibility-mutability.md:75 src/getting-started/basics/counter.md:56 From d05f78eaaf4094456700e82ca68e34fd022bb3fb Mon Sep 17 00:00:00 2001 From: Adetula Olamide <67712894+LamsyA@users.noreply.github.com> Date: Thu, 8 Aug 2024 05:12:35 +0100 Subject: [PATCH 26/36] Add Dict Cheatsheet (#235) * simple_vault test implementation * Added dict cheatsheet * Added dict cheatsheet * a little clean up * a little clean up --- Scarb.lock | 7 +++++++ .../cairo_cheatsheet/src/dict_example.cairo | 16 ++++++++++++++++ .../cairo_cheatsheet/src/lib.cairo | 1 + src/SUMMARY.md | 1 + src/getting-started/cairo_cheatsheet/dict.md | 12 ++++++++++++ 5 files changed, 37 insertions(+) create mode 100644 listings/getting-started/cairo_cheatsheet/src/dict_example.cairo create mode 100644 src/getting-started/cairo_cheatsheet/dict.md diff --git a/Scarb.lock b/Scarb.lock index 96d58087..c84915d1 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -131,9 +131,16 @@ dependencies = [ "openzeppelin", ] +[[package]] +name = "simple_storage" +version = "0.1.0" + [[package]] name = "simple_vault" version = "0.1.0" +dependencies = [ + "erc20", +] [[package]] name = "snforge_std" diff --git a/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo b/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo new file mode 100644 index 00000000..13a70676 --- /dev/null +++ b/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo @@ -0,0 +1,16 @@ +// ANCHOR: sheet +fn dict() { + let mut Auctions: Felt252Dict = Default::default(); + + Auctions.insert('Bola', 30); + Auctions.insert('Maria', 40); + + let bola_balance = Auctions.get('Bola'); + assert!(bola_balance == 30, "Bola balance should be 30"); + + let maria_balance = Auctions.get('Maria'); + assert!(maria_balance == 40, "Maria balance should be 40"); +} +// ANCHOR_END: sheet + + diff --git a/listings/getting-started/cairo_cheatsheet/src/lib.cairo b/listings/getting-started/cairo_cheatsheet/src/lib.cairo index b495fd77..246ffb4c 100644 --- a/listings/getting-started/cairo_cheatsheet/src/lib.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/lib.cairo @@ -9,3 +9,4 @@ mod struct_example; mod type_casting_example; mod while_let_example; mod if_let_example; +mod dict_example; diff --git a/src/SUMMARY.md b/src/SUMMARY.md index ae79c8d8..8079da68 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -41,6 +41,7 @@ Summary - [Tuples](./getting-started/cairo_cheatsheet/tuples.md) - [Struct](./getting-started/cairo_cheatsheet/struct.md) - [Type casting](./getting-started/cairo_cheatsheet/type_casting.md) + - [Dict](./getting-started/cairo_cheatsheet/dict.md) # Components diff --git a/src/getting-started/cairo_cheatsheet/dict.md b/src/getting-started/cairo_cheatsheet/dict.md new file mode 100644 index 00000000..eaa654ae --- /dev/null +++ b/src/getting-started/cairo_cheatsheet/dict.md @@ -0,0 +1,12 @@ +# Dictionary + +A dictionary is a data structure used to store key-value pairs, enabling efficient data retrieval. The keys and values in a Cairo dictionary can be of various types, including Felt252. Dictionaries provide fast access to data, as they allow for quick lookups, insertions, and deletions based on the keys.The core functionality of a `Felt252Dict` is implemented in the trait `Felt252DictTrait`, which includes all basic operations. Among them, we can find: + +- `insert(felt252, T) -> ()` to write values to a dictionary instance. +- `get(felt252) -> T` to read values from it. + +For example: + +```rust +{{#include ../../../listings/getting-started/cairo_cheatsheet/src/dict_example.cairo:sheet}} +``` From afd0d90824591bf0e8c024c59e337d83ed1fdc8b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Nenad=20Misi=C4=87?= Date: Thu, 8 Aug 2024 06:13:10 +0200 Subject: [PATCH 27/36] Mention Foundry is an option for tests (#240) --- CONTRIBUTING.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index fe1e061e..eb2aa6a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -127,7 +127,7 @@ mod contract; mod tests; ``` -> About Starknet Foundry: It is currently not possible to use Starknet Foundry but we are working on it. +You can also use Starknet Foundry to write and run your tests. ### Use of anchor From e5467e4c6383c9b6c8b2fc07683fec70ff794983 Mon Sep 17 00:00:00 2001 From: Strong Force <64266194+princeibs@users.noreply.github.com> Date: Thu, 8 Aug 2024 06:30:35 +0100 Subject: [PATCH 28/36] Update SUMMARY.md (#239) - Add link to NFT Dutch Auction chapter --- src/SUMMARY.md | 1 + 1 file changed, 1 insertion(+) diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 8079da68..4ddbf67a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -57,6 +57,7 @@ Summary - [Upgradeable Contract](./applications/upgradeable_contract.md) - [Defi Vault](./applications/simple_vault.md) - [ERC20 Token](./applications/erc20.md) +- [NFT Dutch Auction](./applications/nft_dutch_auction.md) - [Constant Product AMM](./applications/constant-product-amm.md) - [TimeLock](./applications/timelock.md) - [Staking](./applications/staking.md) From 393af3d7a1d77210fe89d78a0406d7c3d8f08eff Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 8 Aug 2024 15:59:34 +0900 Subject: [PATCH 29/36] fix: links, typos, preludes... (#241) --- .../src/ecdsa_verification.cairo | 46 +++- .../ecdsa_verification/src/lib.cairo | 3 - .../ecdsa_verification/src/tests.cairo | 41 ---- .../advanced_factory/src/contract.cairo | 2 +- .../advanced_factory/src/tests.cairo | 1 - .../applications/components/src/ownable.cairo | 1 - .../constant_product_amm/src/contracts.cairo | 1 - .../crowdfunding/src/campaign.cairo | 2 +- .../crowdfunding/src/mock_upgrade.cairo | 2 +- .../applications/crowdfunding/src/tests.cairo | 1 - .../nft_dutch_auction/src/erc721.cairo | 3 +- .../nft_dutch_auction/src/lib.cairo | 3 - .../src/nft_dutch_auction.cairo | 219 ++++++++++++++++++ .../nft_dutch_auction/src/tests.cairo | 215 ----------------- .../simple_vault/src/simple_vault.cairo | 36 ++- .../applications/staking/src/contract.cairo | 1 - .../staking/src/tests/staking_tests.cairo | 1 - .../src/upgradeable_contract_v0.cairo | 1 - .../cairo_cheatsheet/src/enum_example.cairo | 1 - src/advanced-concepts/library_calls.md | 2 +- .../signature_verification.md | 4 +- src/applications/nft_dutch_auction.md | 5 +- src/getting-started/cairo_cheatsheet/enums.md | 2 +- .../interacting/calling_other_contracts.md | 2 +- .../interacting/interfaces-traits.md | 2 +- 25 files changed, 285 insertions(+), 312 deletions(-) delete mode 100644 listings/advanced-concepts/ecdsa_verification/src/tests.cairo delete mode 100644 listings/applications/nft_dutch_auction/src/tests.cairo diff --git a/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo b/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo index fef18549..8fd21af0 100644 --- a/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo +++ b/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo @@ -1,11 +1,12 @@ +// ANCHOR: contract +use core::starknet::eth_address::EthAddress; +use starknet::secp256_trait::{Signature}; + // How to Sign and Verify // # Signing // 1. Create message to sign // 2. Hash the message // 3. Sign the hash (off chain, keep your private key secret) - -use core::starknet::eth_address::EthAddress; -use starknet::secp256_trait::{Signature}; #[starknet::interface] trait IVerifySignature { fn get_signature(self: @TContractState, r: u256, s: u256, v: u32,) -> Signature; @@ -29,7 +30,6 @@ mod verifySignature { Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key, is_signature_entry_valid }; - use core::traits::{TryInto, Into}; use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; #[storage] @@ -58,7 +58,6 @@ mod verifySignature { signature } - /// Verifies an Ethereum signature. /// /// # Arguments @@ -95,4 +94,41 @@ mod verifySignature { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod tests { + use starknet::secp256_trait::{ + Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key, + is_signature_entry_valid + }; + use starknet::EthAddress; + use starknet::secp256k1::{Secp256k1Point}; + use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; + fn get_message_and_signature() -> (u256, Signature, EthAddress) { + let msg_hash = 0x546ec3fa4f7d3308931816fafd47fa297afe9ac9a09651f77acc13c05a84734f; + let r = 0xc0f30bcef72974dedaf165cf7848a83b0b9eb6a65167a14643df96698d753efb; + let s = 0x7f189e3cb5eb992d8cd26e287a13e900326b87f58da2b7fb48fbd3977e3cab1c; + let v = 27; + + let eth_address = 0x5F04693482cfC121FF244cB3c3733aF712F9df02_u256.into(); + let signature: Signature = signature_from_vrs(v, r, s); + + (msg_hash, signature, eth_address) + } + + #[test] + fn test_verify_eth_signature() { + let (msg_hash, signature, eth_address) = get_message_and_signature(); + verify_eth_signature(:msg_hash, :signature, :eth_address); + } + + #[test] + fn test_secp256k1_recover_public_key() { + let (msg_hash, signature, eth_address) = get_message_and_signature(); + let public_key_point = recover_public_key::(msg_hash, signature).unwrap(); + let calculated_eth_address = public_key_point_to_eth_address(:public_key_point); + assert_eq!(calculated_eth_address, eth_address); + } +} diff --git a/listings/advanced-concepts/ecdsa_verification/src/lib.cairo b/listings/advanced-concepts/ecdsa_verification/src/lib.cairo index 3cfe89c7..6e6bcd1f 100644 --- a/listings/advanced-concepts/ecdsa_verification/src/lib.cairo +++ b/listings/advanced-concepts/ecdsa_verification/src/lib.cairo @@ -1,4 +1 @@ mod ecdsa_verification; - -#[cfg(test)] -mod tests; diff --git a/listings/advanced-concepts/ecdsa_verification/src/tests.cairo b/listings/advanced-concepts/ecdsa_verification/src/tests.cairo deleted file mode 100644 index 0a062284..00000000 --- a/listings/advanced-concepts/ecdsa_verification/src/tests.cairo +++ /dev/null @@ -1,41 +0,0 @@ -#[cfg(test)] -mod tests { - use starknet::secp256_trait::{ - Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key, - is_signature_entry_valid - }; - - use starknet::EthAddress; - use starknet::secp256k1::{Secp256k1Point}; - use core::traits::{TryInto, Into}; - use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; - - - fn get_message_and_signature() -> (u256, Signature, EthAddress) { - let msg_hash = 0x546ec3fa4f7d3308931816fafd47fa297afe9ac9a09651f77acc13c05a84734f; - let r = 0xc0f30bcef72974dedaf165cf7848a83b0b9eb6a65167a14643df96698d753efb; - let s = 0x7f189e3cb5eb992d8cd26e287a13e900326b87f58da2b7fb48fbd3977e3cab1c; - let v = 27; - - let eth_address = 0x5F04693482cfC121FF244cB3c3733aF712F9df02_u256.into(); - let signature: Signature = signature_from_vrs(v, r, s); - - (msg_hash, signature, eth_address) - } - - #[test] - #[available_gas(100000000)] - fn test_verify_eth_signature() { - let (msg_hash, signature, eth_address) = get_message_and_signature(); - verify_eth_signature(:msg_hash, :signature, :eth_address); - } - - #[test] - #[available_gas(100000000)] - fn test_secp256k1_recover_public_key() { - let (msg_hash, signature, eth_address) = get_message_and_signature(); - let public_key_point = recover_public_key::(msg_hash, signature).unwrap(); - let calculated_eth_address = public_key_point_to_eth_address(:public_key_point); - assert(calculated_eth_address == eth_address, 'Invalid Address'); - } -} diff --git a/listings/applications/advanced_factory/src/contract.cairo b/listings/applications/advanced_factory/src/contract.cairo index c07000f5..d3f03432 100644 --- a/listings/applications/advanced_factory/src/contract.cairo +++ b/listings/applications/advanced_factory/src/contract.cairo @@ -21,7 +21,7 @@ pub trait ICampaignFactory { #[starknet::contract] pub mod CampaignFactory { - use core::num::traits::zero::Zero; + use core::num::traits::Zero; use starknet::{ ContractAddress, ClassHash, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, get_contract_address diff --git a/listings/applications/advanced_factory/src/tests.cairo b/listings/applications/advanced_factory/src/tests.cairo index eb0cc1b5..437a9f87 100644 --- a/listings/applications/advanced_factory/src/tests.cairo +++ b/listings/applications/advanced_factory/src/tests.cairo @@ -1,4 +1,3 @@ -use core::traits::TryInto; use core::clone::Clone; use core::result::ResultTrait; use advanced_factory::contract::{ diff --git a/listings/applications/components/src/ownable.cairo b/listings/applications/components/src/ownable.cairo index 68919dd7..e827c741 100644 --- a/listings/applications/components/src/ownable.cairo +++ b/listings/applications/components/src/ownable.cairo @@ -134,7 +134,6 @@ mod test { use starknet::ContractAddress; use starknet::{syscalls::deploy_syscall, SyscallResultTrait, contract_address_const}; use starknet::testing::{set_caller_address, set_contract_address}; - use core::traits::TryInto; use core::num::traits::Zero; fn deploy() -> (IOwnableDispatcher, ContractAddress) { diff --git a/listings/applications/constant_product_amm/src/contracts.cairo b/listings/applications/constant_product_amm/src/contracts.cairo index 0b1bd679..5ac44d74 100644 --- a/listings/applications/constant_product_amm/src/contracts.cairo +++ b/listings/applications/constant_product_amm/src/contracts.cairo @@ -10,7 +10,6 @@ pub trait IConstantProductAmm { #[starknet::contract] pub mod ConstantProductAmm { - use core::traits::Into; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ ContractAddress, get_caller_address, get_contract_address, contract_address_const diff --git a/listings/applications/crowdfunding/src/campaign.cairo b/listings/applications/crowdfunding/src/campaign.cairo index e5b5faf6..c8892de0 100644 --- a/listings/applications/crowdfunding/src/campaign.cairo +++ b/listings/applications/crowdfunding/src/campaign.cairo @@ -33,7 +33,7 @@ pub trait ICampaign { #[starknet::contract] pub mod Campaign { use components::ownable::ownable_component::OwnableInternalTrait; - use core::num::traits::zero::Zero; + use core::num::traits::Zero; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, contract_address_const, diff --git a/listings/applications/crowdfunding/src/mock_upgrade.cairo b/listings/applications/crowdfunding/src/mock_upgrade.cairo index 33348d16..27e9f1b5 100644 --- a/listings/applications/crowdfunding/src/mock_upgrade.cairo +++ b/listings/applications/crowdfunding/src/mock_upgrade.cairo @@ -1,7 +1,7 @@ #[starknet::contract] pub mod MockUpgrade { use components::ownable::ownable_component::OwnableInternalTrait; - use core::num::traits::zero::Zero; + use core::num::traits::Zero; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, contract_address_const, diff --git a/listings/applications/crowdfunding/src/tests.cairo b/listings/applications/crowdfunding/src/tests.cairo index fde363c2..6aff05d8 100644 --- a/listings/applications/crowdfunding/src/tests.cairo +++ b/listings/applications/crowdfunding/src/tests.cairo @@ -1,4 +1,3 @@ -use core::traits::TryInto; use core::clone::Clone; use core::result::ResultTrait; use starknet::{ diff --git a/listings/applications/nft_dutch_auction/src/erc721.cairo b/listings/applications/nft_dutch_auction/src/erc721.cairo index 9e1733ca..51ec11b6 100644 --- a/listings/applications/nft_dutch_auction/src/erc721.cairo +++ b/listings/applications/nft_dutch_auction/src/erc721.cairo @@ -25,8 +25,7 @@ mod ERC721 { // library imports //////////////////////////////// use starknet::{ContractAddress, get_caller_address}; - use core::traits::TryInto; - use core::num::traits::zero::Zero; + use core::num::traits::Zero; //////////////////////////////// // storage variables diff --git a/listings/applications/nft_dutch_auction/src/lib.cairo b/listings/applications/nft_dutch_auction/src/lib.cairo index 33c4ff24..a3ec1cc8 100644 --- a/listings/applications/nft_dutch_auction/src/lib.cairo +++ b/listings/applications/nft_dutch_auction/src/lib.cairo @@ -1,5 +1,2 @@ pub mod nft_dutch_auction; pub mod erc721; - -#[cfg(test)] -pub mod tests; diff --git a/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo b/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo index 2ffaf7e5..aa85dd80 100644 --- a/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo +++ b/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo @@ -1,3 +1,4 @@ +// ANCHOR: contract use starknet::ContractAddress; #[starknet::interface] @@ -133,3 +134,221 @@ pub mod NFTDutchAuction { } } } +// ANCHOR_END: contract + +#[cfg(test)] +mod tests { + use starknet::ContractAddress; + use snforge_std::{ + BlockId, declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan, + cheat_block_timestamp + }; + use nft_dutch_auction::erc721::{IERC721Dispatcher, IERC721DispatcherTrait}; + use super::{INFTDutchAuctionDispatcher, INFTDutchAuctionDispatcherTrait}; + use erc20::token::{IERC20Dispatcher, IERC20DispatcherTrait}; + + // ERC721 token + pub const erc721_name: felt252 = 'My NFT'; + pub const erc721_symbol: felt252 = 'MNFT'; + + // ERC20 token + pub const erc20_name: felt252 = 'My Token'; + pub const erc20_symbol: felt252 = 'MTKN'; + pub const erc20_recipient: felt252 = 'admin'; + pub const erc20_decimals: u8 = 1_u8; + pub const erc20_initial_supply: u128 = 10000_u128; + + // NFT Auction + pub const starting_price: felt252 = 500; + pub const seller: felt252 = 'seller'; + pub const duration: felt252 = 60; // in seconds + pub const discount_rate: felt252 = 5; + pub const total_supply: felt252 = 2; + + fn get_contract_addresses() -> (ContractAddress, ContractAddress, ContractAddress) { + let erc721 = declare("ERC721").unwrap(); + let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; + let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); + let erc20 = declare("erc20").unwrap(); + let erc20_constructor_calldata = array![ + erc20_recipient, + erc20_name, + erc20_decimals.into(), + erc20_initial_supply.into(), + erc20_symbol + ]; + let (erc20_address, _) = erc20.deploy(@erc20_constructor_calldata).unwrap(); + let nft_auction = declare("NFTDutchAuction").unwrap(); + let nft_auction_constructor_calldata = array![ + erc20_address.into(), + erc721_address.into(), + starting_price, + seller, + duration, + discount_rate, + total_supply + ]; + let (nft_auction_address, _) = nft_auction + .deploy(@nft_auction_constructor_calldata) + .unwrap(); + (erc721_address, erc20_address, nft_auction_address) + } + + #[test] + fn test_buy() { + let (erc721_address, erc20_address, nft_auction_address) = get_contract_addresses(); + let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); + let seller: ContractAddress = 'seller'.try_into().unwrap(); + let buyer: ContractAddress = 'buyer'.try_into().unwrap(); + + // Transfer erc20 tokens to buyer + assert_eq!(erc20_dispatcher.balance_of(buyer), 0.into()); + cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); + let transfer_amt = 5000; + erc20_dispatcher.transfer(buyer, transfer_amt.into()); + assert_eq!(erc20_dispatcher.balance_of(buyer), transfer_amt.into()); + + // Buy token + cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(3)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(2)); + + let nft_id_1 = 1; + let seller_bal_before_buy = erc20_dispatcher.balance_of(seller); + let buyer_bal_before_buy = erc20_dispatcher.balance_of(buyer); + let nft_price = nft_auction_dispatcher.get_price().into(); + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + + nft_auction_dispatcher.buy(nft_id_1); + + let seller_bal_after_buy = erc20_dispatcher.balance_of(seller); + let buyer_bal_after_buy = erc20_dispatcher.balance_of(buyer); + + assert_eq!(seller_bal_after_buy, seller_bal_before_buy + nft_price); + assert_eq!(buyer_bal_after_buy, buyer_bal_before_buy - nft_price); + assert_eq!(erc721_dispatcher.owner_of(nft_id_1), buyer); + + // Forward block timestamp in order for a reduced nft price + let forward_blocktime_by = 4000; // milliseconds + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + // Buy token again after some time + let nft_id_2 = 2; + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + + assert_ne!(erc721_dispatcher.owner_of(nft_id_2), buyer); + nft_auction_dispatcher.buy(nft_id_2); + assert_eq!(erc721_dispatcher.owner_of(nft_id_2), buyer); + } + + #[test] + #[should_panic(expected: 'auction has ended')] + fn test_buy_should_panic_when_total_supply_reached() { + let (_, erc20_address, nft_auction_address) = get_contract_addresses(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); + let buyer: ContractAddress = 'buyer'.try_into().unwrap(); + + // Transfer erc20 tokens to buyer + cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); + let transfer_amt = 5000; + erc20_dispatcher.transfer(buyer, transfer_amt.into()); + + // Buy token + cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(4)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(3)); + + let nft_id_1 = 1; + let nft_price = nft_auction_dispatcher.get_price().into(); + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_1); + + // Forward block timestamp in order for a reduced nft price + let forward_blocktime_by = 4000; // 4 seconds (in milliseconds) + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + // Buy token again after some time + let nft_id_2 = 2; + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_2); + + // Buy token again after the total supply has reached + let nft_id_3 = 3; + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_3); + } + + #[test] + #[should_panic(expected: 'auction has ended')] + fn test_buy_should_panic_when_duration_ended() { + let (_, erc20_address, nft_auction_address) = get_contract_addresses(); + let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); + let buyer: ContractAddress = 'buyer'.try_into().unwrap(); + + // Transfer erc20 tokens to buyer + cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); + let transfer_amt = 5000; + erc20_dispatcher.transfer(buyer, transfer_amt.into()); + + // Buy token + cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(4)); + cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(3)); + + let nft_id_1 = 1; + let nft_price = nft_auction_dispatcher.get_price().into(); + + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_1); + + // Forward block timestamp to a time after duration has ended + // During deployment, duration was set to 60 seconds + let forward_blocktime_by = 61000; // 61 seconds (in milliseconds) + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + // Buy token again after some time + let nft_id_2 = 2; + // buyer approves nft auction contract to spend own erc20 token + erc20_dispatcher.approve(nft_auction_address, nft_price); + nft_auction_dispatcher.buy(nft_id_2); + } + + #[test] + fn test_price_decreases_after_some_time() { + let (_, _, nft_auction_address) = get_contract_addresses(); + let nft_auction_dispatcher = INFTDutchAuctionDispatcher { + contract_address: nft_auction_address + }; + + let nft_price_before_time_travel = nft_auction_dispatcher.get_price(); + + // Forward time + let forward_blocktime_by = 10000; // 10 seconds (in milliseconds) + cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); + + let nft_price_after_time_travel = nft_auction_dispatcher.get_price(); + + println!("price: {:?}", nft_price_after_time_travel); + + assert_gt!(nft_price_before_time_travel, nft_price_after_time_travel); + } +} diff --git a/listings/applications/nft_dutch_auction/src/tests.cairo b/listings/applications/nft_dutch_auction/src/tests.cairo deleted file mode 100644 index be89a25f..00000000 --- a/listings/applications/nft_dutch_auction/src/tests.cairo +++ /dev/null @@ -1,215 +0,0 @@ -use core::option::OptionTrait; -use core::traits::{Into, TryInto}; -use starknet::ContractAddress; -use snforge_std::{ - BlockId, declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan, - cheat_block_timestamp -}; -use super::{ - erc721::{IERC721Dispatcher, IERC721DispatcherTrait}, - nft_dutch_auction::{INFTDutchAuctionDispatcher, INFTDutchAuctionDispatcherTrait} -}; -use erc20::token::{IERC20Dispatcher, IERC20DispatcherTrait}; - -// ERC721 token -pub const erc721_name: felt252 = 'My NFT'; -pub const erc721_symbol: felt252 = 'MNFT'; - -// ERC20 token -pub const erc20_name: felt252 = 'My Token'; -pub const erc20_symbol: felt252 = 'MTKN'; -pub const erc20_recipient: felt252 = 'admin'; -pub const erc20_decimals: u8 = 1_u8; -pub const erc20_initial_supply: u128 = 10000_u128; - -// NFT Auction -pub const starting_price: felt252 = 500; -pub const seller: felt252 = 'seller'; -pub const duration: felt252 = 60; // in seconds -pub const discount_rate: felt252 = 5; -pub const total_supply: felt252 = 2; - -fn get_contract_addresses() -> (ContractAddress, ContractAddress, ContractAddress) { - let erc721 = declare("ERC721").unwrap(); - let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; - let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); - let erc20 = declare("erc20").unwrap(); - let erc20_constructor_calldata = array![ - erc20_recipient, - erc20_name, - erc20_decimals.into(), - erc20_initial_supply.into(), - erc20_symbol - ]; - let (erc20_address, _) = erc20.deploy(@erc20_constructor_calldata).unwrap(); - let nft_auction = declare("NFTDutchAuction").unwrap(); - let nft_auction_constructor_calldata = array![ - erc20_address.into(), - erc721_address.into(), - starting_price, - seller, - duration, - discount_rate, - total_supply - ]; - let (nft_auction_address, _) = nft_auction.deploy(@nft_auction_constructor_calldata).unwrap(); - (erc721_address, erc20_address, nft_auction_address) -} - -#[test] -fn test_buy() { - let (erc721_address, erc20_address, nft_auction_address) = get_contract_addresses(); - let erc721_dispatcher = IERC721Dispatcher { contract_address: erc721_address }; - let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; - let nft_auction_dispatcher = INFTDutchAuctionDispatcher { - contract_address: nft_auction_address - }; - let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); - let seller: ContractAddress = 'seller'.try_into().unwrap(); - let buyer: ContractAddress = 'buyer'.try_into().unwrap(); - - // Transfer erc20 tokens to buyer - assert_eq!(erc20_dispatcher.balance_of(buyer), 0.into()); - cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); - let transfer_amt = 5000; - erc20_dispatcher.transfer(buyer, transfer_amt.into()); - assert_eq!(erc20_dispatcher.balance_of(buyer), transfer_amt.into()); - - // Buy token - cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(3)); - cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(2)); - - let nft_id_1 = 1; - let seller_bal_before_buy = erc20_dispatcher.balance_of(seller); - let buyer_bal_before_buy = erc20_dispatcher.balance_of(buyer); - let nft_price = nft_auction_dispatcher.get_price().into(); - - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - - nft_auction_dispatcher.buy(nft_id_1); - - let seller_bal_after_buy = erc20_dispatcher.balance_of(seller); - let buyer_bal_after_buy = erc20_dispatcher.balance_of(buyer); - - assert_eq!(seller_bal_after_buy, seller_bal_before_buy + nft_price); - assert_eq!(buyer_bal_after_buy, buyer_bal_before_buy - nft_price); - assert_eq!(erc721_dispatcher.owner_of(nft_id_1), buyer); - - // Forward block timestamp in order for a reduced nft price - let forward_blocktime_by = 4000; // milliseconds - cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); - - // Buy token again after some time - let nft_id_2 = 2; - - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - - assert_ne!(erc721_dispatcher.owner_of(nft_id_2), buyer); - nft_auction_dispatcher.buy(nft_id_2); - assert_eq!(erc721_dispatcher.owner_of(nft_id_2), buyer); -} - -#[test] -#[should_panic(expected: 'auction has ended')] -fn test_buy_should_panic_when_total_supply_reached() { - let (_, erc20_address, nft_auction_address) = get_contract_addresses(); - let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; - let nft_auction_dispatcher = INFTDutchAuctionDispatcher { - contract_address: nft_auction_address - }; - let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); - let buyer: ContractAddress = 'buyer'.try_into().unwrap(); - - // Transfer erc20 tokens to buyer - cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); - let transfer_amt = 5000; - erc20_dispatcher.transfer(buyer, transfer_amt.into()); - - // Buy token - cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(4)); - cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(3)); - - let nft_id_1 = 1; - let nft_price = nft_auction_dispatcher.get_price().into(); - - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - nft_auction_dispatcher.buy(nft_id_1); - - // Forward block timestamp in order for a reduced nft price - let forward_blocktime_by = 4000; // 4 seconds (in milliseconds) - cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); - - // Buy token again after some time - let nft_id_2 = 2; - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - nft_auction_dispatcher.buy(nft_id_2); - - // Buy token again after the total supply has reached - let nft_id_3 = 3; - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - nft_auction_dispatcher.buy(nft_id_3); -} - -#[test] -#[should_panic(expected: 'auction has ended')] -fn test_buy_should_panic_when_duration_ended() { - let (_, erc20_address, nft_auction_address) = get_contract_addresses(); - let erc20_dispatcher = IERC20Dispatcher { contract_address: erc20_address }; - let nft_auction_dispatcher = INFTDutchAuctionDispatcher { - contract_address: nft_auction_address - }; - let erc20_admin: ContractAddress = 'admin'.try_into().unwrap(); - let buyer: ContractAddress = 'buyer'.try_into().unwrap(); - - // Transfer erc20 tokens to buyer - cheat_caller_address(erc20_address, erc20_admin, CheatSpan::TargetCalls(1)); - let transfer_amt = 5000; - erc20_dispatcher.transfer(buyer, transfer_amt.into()); - - // Buy token - cheat_caller_address(nft_auction_address, buyer, CheatSpan::TargetCalls(4)); - cheat_caller_address(erc20_address, buyer, CheatSpan::TargetCalls(3)); - - let nft_id_1 = 1; - let nft_price = nft_auction_dispatcher.get_price().into(); - - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - nft_auction_dispatcher.buy(nft_id_1); - - // Forward block timestamp to a time after duration has ended - // During deployment, duration was set to 60 seconds - let forward_blocktime_by = 61000; // 61 seconds (in milliseconds) - cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); - - // Buy token again after some time - let nft_id_2 = 2; - // buyer approves nft auction contract to spend own erc20 token - erc20_dispatcher.approve(nft_auction_address, nft_price); - nft_auction_dispatcher.buy(nft_id_2); -} - -#[test] -fn test_price_decreases_after_some_time() { - let (_, _, nft_auction_address) = get_contract_addresses(); - let nft_auction_dispatcher = INFTDutchAuctionDispatcher { - contract_address: nft_auction_address - }; - - let nft_price_before_time_travel = nft_auction_dispatcher.get_price(); - - // Forward time - let forward_blocktime_by = 10000; // 10 seconds (in milliseconds) - cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); - - let nft_price_after_time_travel = nft_auction_dispatcher.get_price(); - - println!("price: {:?}", nft_price_after_time_travel); - - assert_gt!(nft_price_before_time_travel, nft_price_after_time_travel); -} diff --git a/listings/applications/simple_vault/src/simple_vault.cairo b/listings/applications/simple_vault/src/simple_vault.cairo index e8c0191b..48b89d97 100644 --- a/listings/applications/simple_vault/src/simple_vault.cairo +++ b/listings/applications/simple_vault/src/simple_vault.cairo @@ -1,3 +1,4 @@ +// ANCHOR: contract use starknet::ContractAddress; // In order to make contract calls within our Vault, @@ -62,12 +63,10 @@ pub mod SimpleVault { self.total_supply.write(self.total_supply.read() - shares); self.balance_of.write(from, self.balance_of.read(from) - shares); } - } #[abi(embed_v0)] impl SimpleVault of super::ISimpleVault { - fn user_balance_of(ref self: ContractState, account: ContractAddress) -> u256 { self.balance_of.read(account) } @@ -76,8 +75,7 @@ pub mod SimpleVault { self.total_supply.read() } - - fn deposit(ref self: ContractState, amount: u256){ + fn deposit(ref self: ContractState, amount: u256) { // a = amount // B = balance of token before deposit // T = total supply @@ -93,13 +91,12 @@ pub mod SimpleVault { if self.total_supply.read() == 0 { shares = amount; } else { - let balance: u256 = self.token.read().balance_of(this).try_into() - .unwrap(); + let balance: u256 = self.token.read().balance_of(this).try_into().unwrap(); shares = (amount * self.total_supply.read()) / balance; } - - PrivateFunctions::_mint(ref self, caller, shares); - + + PrivateFunctions::_mint(ref self, caller, shares); + let amount_felt252: felt252 = amount.low.into(); self.token.read().transfer_from(caller, this, amount_felt252); } @@ -124,23 +121,19 @@ pub mod SimpleVault { } } } -// ANCHOR_END: simple_vault +// ANCHOR_END: contract #[cfg(test)] mod tests { - use core::traits::Into; use super::{ SimpleVault, ISimpleVaultDispatcher, ISimpleVaultDispatcherTrait, IERC20Dispatcher, IERC20DispatcherTrait }; - - // use erc20::token::IERC20; - use erc20::token::{IERC20DispatcherTrait as IERC20DispatcherTrait_token, + use erc20::token::{ + IERC20DispatcherTrait as IERC20DispatcherTrait_token, IERC20Dispatcher as IERC20Dispatcher_token }; - use core::num::traits::Zero; - use starknet::testing::{set_contract_address, set_account_contract_address}; use starknet::{ ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, @@ -198,9 +191,8 @@ mod tests { let balance_of_caller = dispatcher.user_balance_of(caller); let total_supply = dispatcher.contract_total_supply(); - assert(balance_of_caller == amount, 'Deposit failed'); - assert(total_supply == amount, 'total supply mismatch'); - + assert_eq!(balance_of_caller, amount); + assert_eq!(total_supply, amount); } #[test] @@ -222,8 +214,6 @@ mod tests { // Check balances of user in the vault after withdraw let balance_of_caller = dispatcher.user_balance_of(caller); - assert(balance_of_caller == 0.into(), 'withdraw failed'); + assert_eq!(balance_of_caller, 0.into()); } - - -} \ No newline at end of file +} diff --git a/listings/applications/staking/src/contract.cairo b/listings/applications/staking/src/contract.cairo index ed17e14d..21134ca0 100644 --- a/listings/applications/staking/src/contract.cairo +++ b/listings/applications/staking/src/contract.cairo @@ -23,7 +23,6 @@ mod Errors { #[starknet::contract] pub mod StakingContract { use core::starknet::event::EventEmitter; - use core::traits::Into; use core::num::traits::Zero; use starknet::{ContractAddress, get_caller_address, get_block_timestamp, get_contract_address}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; diff --git a/listings/applications/staking/src/tests/staking_tests.cairo b/listings/applications/staking/src/tests/staking_tests.cairo index 6ff1e1c8..4e53031b 100644 --- a/listings/applications/staking/src/tests/staking_tests.cairo +++ b/listings/applications/staking/src/tests/staking_tests.cairo @@ -7,7 +7,6 @@ mod tests { use staking::contract::StakingContract::__member_module_balance_of::InternalContractMemberStateTrait as BalInt; use staking::contract::StakingContract::__member_module_staking_token::InternalContractMemberStateTrait as StakeTokInt; use staking::contract::StakingContract::__member_module_reward_token::InternalContractMemberStateTrait as RewardTokInt; - use core::traits::TryInto; use openzeppelin::token::erc20::interface::IERC20DispatcherTrait; use openzeppelin::token::erc20::erc20::ERC20Component::InternalTrait; use staking::contract::IStakingContractDispatcherTrait; diff --git a/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo b/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo index e68d4251..a6e1b402 100644 --- a/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo +++ b/listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo @@ -16,7 +16,6 @@ pub mod UpgradeableContract_V0 { #[storage] struct Storage {} - #[event] #[derive(Copy, Drop, Debug, PartialEq, starknet::Event)] pub enum Event { diff --git a/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo b/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo index 5e632ee6..8364485c 100644 --- a/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo @@ -32,7 +32,6 @@ trait IEnumContract { #[starknet::contract] mod EnumContract { use core::clone::Clone; - use core::traits::Into; use super::IEnumContract; use super::{Action, Position, UserCommand}; diff --git a/src/advanced-concepts/library_calls.md b/src/advanced-concepts/library_calls.md index f33aff17..40460cb8 100644 --- a/src/advanced-concepts/library_calls.md +++ b/src/advanced-concepts/library_calls.md @@ -6,7 +6,7 @@ With Contract dispatcher we are calling an already deployed contract (with assoc Contract dispatcher call is synonymous to external calls in Solidity, while library dispatcher call is synonymous to delegate call. -For further reading: [Cairo book](https://book.cairo-lang.org/ch15-02-contract-dispatchers-library-dispatchers-and-system-calls.html?highlight=library%20dispatchers#library-dispatcher). +For further reading: [Cairo book](https://book.cairo-lang.org/ch15-03-executing-code-from-another-class.html#library-calls) ```rust {{#rustdoc_include ../../listings/advanced-concepts/library_calls/src/library_call.cairo:library_dispatcher}} diff --git a/src/advanced-concepts/signature_verification.md b/src/advanced-concepts/signature_verification.md index 7c51ca50..278cb1be 100644 --- a/src/advanced-concepts/signature_verification.md +++ b/src/advanced-concepts/signature_verification.md @@ -4,7 +4,5 @@ This is the Cairo adaptation of the [Solidity by Example - Verifying Signature]( Messages can be signed off chain and then verified on chain using a smart contract. ```rust -{{#include ../../listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo}} +{{#rustdoc_include ../../listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo:contract}} ``` - -[Click here to interact with the deployed contract on Voyager](https://goerli.voyager.online/contract/0x070bad62072d2a30fd08a95e9de99828955cfcffc40eac8adf3b21e9970590be#writeContract) diff --git a/src/applications/nft_dutch_auction.md b/src/applications/nft_dutch_auction.md index 765b3736..7df20588 100644 --- a/src/applications/nft_dutch_auction.md +++ b/src/applications/nft_dutch_auction.md @@ -3,6 +3,7 @@ This is the Cairo adaptation (with some modifications) of the [Solidity by example NFT Dutch Auction](https://solidity-by-example.org/app/dutch-auction/). Here's how it works: + - The seller of the NFT deploys this contract with a startingPrice. - The auction lasts for a specified duration. - The price decreases over time. @@ -10,5 +11,5 @@ Here's how it works: - The auction ends when either the totalSupply is reached or the duration has elapsed. ```rust -{{#include ../../listings/applications/nft_auction/src/nft_auction.cairo}} -``` \ No newline at end of file +{{#rustdoc_include ../../listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo:contract}} +``` diff --git a/src/getting-started/cairo_cheatsheet/enums.md b/src/getting-started/cairo_cheatsheet/enums.md index ba5694fe..9b48967b 100644 --- a/src/getting-started/cairo_cheatsheet/enums.md +++ b/src/getting-started/cairo_cheatsheet/enums.md @@ -14,7 +14,7 @@ Enums can be declared both inside and outside a contract. If declared outside, t - It is possible to store `enums` in the contract storage. But unlike most of the core library types which implement the `Store` trait, enums are custom types and therefore do not automatically implement the `Store` trait. The enum as well as all of its variants have to explicitly implement the `Store` trait in order for it to be stored inside a contract storage. - - If all of its variants implement the `Store` trait, implementing the `Store` trait on the enum is as simple as deriving it, using `#[derive(starknet::Store)]` (as shown in example above). If not, the `Store` trait has to be manually implemented -- see an example of manually implementing the `Store` trait for a complex type in chapter [Storing Arrays](https://starknet-by-example.voyager.online/ch02/storing_arrays.html). + - If all of its variants implement the `Store` trait, implementing the `Store` trait on the enum is as simple as deriving it, using `#[derive(starknet::Store)]` (as shown in example above). If not, the `Store` trait has to be manually implemented -- see an example of manually implementing the `Store` trait for a complex type in chapter [Storing Arrays](../../advanced-concepts/storing_arrays.md). 2. Enums as parameters and return values to entrypoints diff --git a/src/getting-started/interacting/calling_other_contracts.md b/src/getting-started/interacting/calling_other_contracts.md index c9b3d89c..027a3a5b 100644 --- a/src/getting-started/interacting/calling_other_contracts.md +++ b/src/getting-started/interacting/calling_other_contracts.md @@ -3,7 +3,7 @@ There are two different ways to call other contracts in Cairo. The easiest way to call other contracts is by using the dispatcher of the contract you want to call. -You can read more about Dispatchers in the [Cairo Book](https://book.cairo-lang.org/ch99-02-02-contract-dispatcher-library-dispatcher-and-system-calls.html#contract-dispatcher). +You can read more about Dispatchers in the [Cairo Book](https://book.cairo-lang.org/ch15-02-interacting-with-another-contract.html#calling-contracts-using-the-contract-dispatcher). The other way is to use the `starknet::call_contract_syscall` syscall yourself. However, this method is not recommended and will not be covered in this chapter. diff --git a/src/getting-started/interacting/interfaces-traits.md b/src/getting-started/interacting/interfaces-traits.md index 54a2e591..4c8ed663 100644 --- a/src/getting-started/interacting/interfaces-traits.md +++ b/src/getting-started/interacting/interfaces-traits.md @@ -1,6 +1,6 @@ # Contract interfaces and Traits generation -Contract interfaces define the structure and behavior of a contract, serving as the contract's public ABI. They list all the function signatures that a contract exposes. For a detailed explanation of interfaces, you can refer to the [Cairo Book](https://book.cairo-lang.org/ch99-01-02-a-simple-contract.html). +Contract interfaces define the structure and behavior of a contract, serving as the contract's public ABI. They list all the function signatures that a contract exposes. For a detailed explanation of interfaces, you can refer to the [Cairo Book](https://book.cairo-lang.org/ch13-02-anatomy-of-a-simple-contract.html#the-interface-the-contracts-blueprint). In Cairo, to specify the interface you need to define a trait annotated with `#[starknet::interface]` and then implement that trait in the contract. From 3b416c6b3cea9dd112cb6c1508dd285011e1e4d8 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 19 Sep 2024 13:26:28 +0800 Subject: [PATCH 30/36] Update Cairo >2.8, `2024_07` edition (#246) * remove List No longer needed with Vec * chore: dependencies update >=2.8 * chore: 2024_07 edition getting-started * chore: Map getting-started * chore: update applications * chore: update dependencies * fix: storage variables PointerReadAccess * doc: update contributor guide * chore: update advanced-concepts * fix: 2024_07 edition * chore: versions update * feat: storage custom types individual members access * remove storing arrays chapter * ci/cd: remove custom test resolver script * chore: switch dependencies to scarb registry * Revert "ci/cd: remove custom test resolver script" This reverts commit 0c3549fd038c3274d7ea748fd8d89f2fee633c43. * chore: split snforge/cairo-test scarb config --- .tool-versions | 4 +- CONTRIBUTING.md | 116 +++++++++++---- LICENSE | 2 +- Scarb.lock | 127 ++++++++++++++--- Scarb.toml | 13 +- .../ecdsa_verification/Scarb.toml | 6 +- .../src/ecdsa_verification.cairo | 22 +-- .../hash_solidity_compatible/Scarb.toml | 5 +- .../src/contract.cairo | 2 +- .../hash_solidity_compatible/src/tests.cairo | 11 +- .../advanced-concepts/hash_trait/Scarb.toml | 5 +- .../hash_trait/src/hash_trait.cairo | 16 +-- .../library_calls/Scarb.toml | 9 +- .../library_calls/src/library_call.cairo | 2 +- .../library_calls/src/tests.cairo | 3 +- .../simple_account/Scarb.toml | 3 +- .../simple_account/src/simple_account.cairo | 1 + .../store_using_packing/Scarb.toml | 5 +- .../store_using_packing/src/contract.cairo | 1 + .../store_using_packing/src/tests.cairo | 5 +- .../storing_arrays/.gitignore | 1 - .../storing_arrays/Scarb.lock | 6 - .../storing_arrays/Scarb.toml | 12 -- .../storing_arrays/src/contract.cairo | 96 ------------- .../storing_arrays/src/lib.cairo | 4 - .../storing_arrays/src/tests.cairo | 62 -------- .../struct_as_mapping_key/Scarb.toml | 5 +- .../struct_as_mapping_key/src/contract.cairo | 4 +- .../struct_as_mapping_key/src/test.cairo | 3 +- .../advanced-concepts/using_lists/.gitignore | 1 - .../advanced-concepts/using_lists/Scarb.lock | 14 -- .../advanced-concepts/using_lists/Scarb.toml | 13 -- .../using_lists/src/contract.cairo | 72 ---------- .../using_lists/src/lib.cairo | 4 - .../using_lists/src/tests.cairo | 101 -------------- .../write_to_any_slot/Scarb.toml | 5 +- .../write_to_any_slot/src/contract.cairo | 3 +- .../write_to_any_slot/src/tests.cairo | 4 +- .../applications/advanced_factory/Scarb.toml | 6 +- .../advanced_factory/src/contract.cairo | 10 +- .../advanced_factory/src/tests.cairo | 38 +++-- listings/applications/components/Scarb.toml | 5 +- .../components/src/countable.cairo | 4 +- .../src/others/switch_collision.cairo | 7 +- .../applications/components/src/ownable.cairo | 10 +- .../components/src/switchable.cairo | 8 +- .../components_dependencies/Scarb.toml | 5 +- .../src/contract_countable.cairo | 26 ++-- .../src/contract_countable_switchable.cairo | 25 ++-- ...ntract_countable_switchable_internal.cairo | 27 ++-- .../src/countable_dep_switch.cairo | 5 +- .../src/countable_internal_dep_switch.cairo | 3 +- .../constant_product_amm/Scarb.toml | 5 +- .../constant_product_amm/src/contracts.cairo | 12 +- .../constant_product_amm/src/tests.cairo | 6 +- listings/applications/crowdfunding/Scarb.toml | 8 +- .../crowdfunding/src/campaign.cairo | 7 +- .../src/campaign/pledgeable.cairo | 38 ++--- .../crowdfunding/src/mock_upgrade.cairo | 7 +- .../applications/crowdfunding/src/tests.cairo | 51 ++++--- listings/applications/erc20/Scarb.toml | 5 +- listings/applications/erc20/src/token.cairo | 13 +- .../applications/nft_dutch_auction/Scarb.toml | 5 +- .../nft_dutch_auction/src/erc721.cairo | 16 ++- .../src/nft_dutch_auction.cairo | 17 +-- .../simple_storage_starknetjs/Scarb.toml | 5 +- .../src/storage.cairo | 2 + listings/applications/simple_vault/Scarb.toml | 5 +- .../applications/simple_vault/src/lib.cairo | 1 - .../simple_vault/src/simple_vault.cairo | 19 ++- listings/applications/staking/Scarb.toml | 5 +- .../applications/staking/src/contract.cairo | 38 ++--- .../staking/src/tests/staking_tests.cairo | 132 +++++++----------- .../staking/src/tests/tokens.cairo | 4 +- listings/applications/timelock/Scarb.toml | 8 +- .../timelock/src/tests/timelock.cairo | 12 +- .../timelock/src/tests/utils.cairo | 6 +- .../applications/timelock/src/timelock.cairo | 10 +- .../upgradeable_contract/Scarb.toml | 5 +- .../upgradeable_contract/src/tests.cairo | 2 +- listings/getting-started/bytearray/Scarb.toml | 5 +- .../bytearray/src/bytearray.cairo | 9 +- .../cairo_cheatsheet/Scarb.toml | 6 +- .../cairo_cheatsheet/src/dict_example.cairo | 2 + .../cairo_cheatsheet/src/enum_example.cairo | 2 +- .../cairo_cheatsheet/src/felt_example.cairo | 2 +- .../cairo_cheatsheet/src/loop_example.cairo | 2 +- .../src/mapping_example.cairo | 5 +- .../cairo_cheatsheet/src/tuple_example.cairo | 2 +- .../src/type_casting_example.cairo | 2 +- .../cairo_cheatsheet/src/while_example.cairo | 2 +- .../src/while_let_example.cairo | 19 ++- .../calling_other_contracts/Scarb.toml | 5 +- .../calling_other_contracts/src/caller.cairo | 17 +-- .../getting-started/constructor/Scarb.toml | 5 +- .../constructor/src/constructor.cairo | 8 +- listings/getting-started/counter/Scarb.toml | 5 +- .../getting-started/counter/src/counter.cairo | 6 +- .../custom_type_serde/Scarb.toml | 5 +- .../custom_type_serde/src/contract.cairo | 2 +- listings/getting-started/errors/Scarb.toml | 5 +- .../errors/src/custom_errors.cairo | 2 +- .../errors/src/simple_errors.cairo | 2 +- .../errors/src/vault_errors.cairo | 5 +- listings/getting-started/events/Scarb.toml | 5 +- .../getting-started/events/src/counter.cairo | 19 ++- listings/getting-started/factory/Scarb.toml | 5 +- .../factory/src/simple_factory.cairo | 10 +- .../interfaces_traits/Scarb.toml | 5 +- .../interfaces_traits/src/explicit.cairo | 6 +- .../interfaces_traits/src/implicit.cairo | 11 +- .../src/implicit_internal.cairo | 6 +- listings/getting-started/mappings/Scarb.toml | 5 +- .../mappings/src/mappings.cairo | 6 +- listings/getting-started/storage/Scarb.toml | 5 +- .../storage/src/contract.cairo | 12 +- .../storage/src/minimal_contract.cairo | 2 +- .../storing_custom_types/Scarb.toml | 5 +- .../storing_custom_types/src/contract.cairo | 31 +++- .../getting-started/testing_how_to/Scarb.toml | 5 +- .../testing_how_to/src/contract.cairo | 27 ++-- listings/getting-started/variables/Scarb.toml | 5 +- .../variables/src/global_variables.cairo | 4 +- .../variables/src/local_variables.cairo | 2 +- .../variables/src/storage_variables.cairo | 12 +- .../getting-started/visibility/Scarb.toml | 5 +- .../visibility/src/visibility.cairo | 9 +- listings/templates/scarb/.gitignore | 1 - listings/templates/scarb/Scarb.toml | 12 -- listings/templates/scarb/src/lib.cairo | 28 ---- src/SUMMARY.md | 4 +- src/advanced-concepts/list.md | 68 --------- src/advanced-concepts/storing_arrays.md | 19 --- src/advanced-concepts/struct-mapping-key.md | 4 +- src/applications/crowdfunding.md | 2 +- src/getting-started/basics/mappings.md | 6 +- .../basics/storing-custom-types.md | 7 + .../cairo_cheatsheet/mapping.md | 4 +- src/starknet-by-example.md | 5 +- 139 files changed, 785 insertions(+), 1093 deletions(-) delete mode 100644 listings/advanced-concepts/storing_arrays/.gitignore delete mode 100644 listings/advanced-concepts/storing_arrays/Scarb.lock delete mode 100644 listings/advanced-concepts/storing_arrays/Scarb.toml delete mode 100644 listings/advanced-concepts/storing_arrays/src/contract.cairo delete mode 100644 listings/advanced-concepts/storing_arrays/src/lib.cairo delete mode 100644 listings/advanced-concepts/storing_arrays/src/tests.cairo delete mode 100644 listings/advanced-concepts/using_lists/.gitignore delete mode 100644 listings/advanced-concepts/using_lists/Scarb.lock delete mode 100644 listings/advanced-concepts/using_lists/Scarb.toml delete mode 100644 listings/advanced-concepts/using_lists/src/contract.cairo delete mode 100644 listings/advanced-concepts/using_lists/src/lib.cairo delete mode 100644 listings/advanced-concepts/using_lists/src/tests.cairo delete mode 100644 listings/templates/scarb/.gitignore delete mode 100644 listings/templates/scarb/Scarb.toml delete mode 100644 listings/templates/scarb/src/lib.cairo delete mode 100644 src/advanced-concepts/list.md delete mode 100644 src/advanced-concepts/storing_arrays.md diff --git a/.tool-versions b/.tool-versions index dc8930c0..120e90d0 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ -scarb 2.6.5 -starknet-foundry 0.25.0 +scarb 2.8.2 +starknet-foundry 0.30.0 \ No newline at end of file diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index eb2aa6a5..49365e54 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,6 +1,6 @@ # Contributing -When contributing to this repository, please first discuss the change you wish to make via issue or in the telegram channel before making a change. +When contributing to this repository, please first discuss the change you wish to make via issue or in the telegram channel before making a change. Join the telegram channel: https://t.me/StarknetByExample @@ -32,6 +32,7 @@ Please note we have a code of conduct, please follow it in all your interactions 1. Clone this repository. 2. Rust related packages: + - Install toolchain providing `cargo` using [rustup](https://rustup.rs/). - Install [mdBook](https://rust-lang.github.io/mdBook/guide/installation.html) and the required extension with `cargo install mdbook mdbook-i18n-helpers mdbook-last-changed `. @@ -60,8 +61,11 @@ You can add or modify examples in the `listings` directory. Each listing is a sc You can find a template of a blank scarb project in the `listings/template` directory. (You can also use `scarb init` to create a new scarb project, but be sure to remove the generated git repository) -Here's the required `Scarb.toml` configuration: - +You can choose to use standard cairo with `cairo-test` or Starknet Foundry with `snforge_std`. +Please use the appropriate `Scarb.toml` configuration. `scarb test` will automatically resolve to `snforge test` if `snforge_std` is in the dependencies. + +Here's the required `Scarb.toml` configuration for **Cairo test**: + ```toml [package] name = "pkg_name" @@ -73,14 +77,45 @@ version.workspace = true [dependencies] starknet.workspace = true # Uncomment the following lines if you want to use additional dependencies: -# Starknet Foundry: -# snforge_std.workspace = true # OpenZeppelin: # openzeppelin.workspace = true # If you want to use another Starknet By Example's listing, you can add it as a dependency like this: # erc20 = { path = "../../getting-started/erc20" } +[dev-dependencies] +cairo_test.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +casm = true +``` + +Here's the required `Scarb.toml` configuration for **Starknet Foundry**: + +```toml +[package] +name = "pkg_name" +version.workspace = true + +# Specify that this can be used as a dependency in another scarb project: +[lib] + +[dependencies] +starknet.workspace = true +snforge_std.workspace = true +# Uncomment the following lines if you want to use additional dependencies: +# OpenZeppelin: +# openzeppelin.workspace = true + +# If you want to use another Starknet By Example's listing, you can add it as a dependency like this: +# erc20 = { path = "../../getting-started/erc20" } + +[dev-dependencies] +assert_macros.workspace = true + [scripts] test.workspace = true @@ -89,6 +124,7 @@ casm = true ``` You also NEED to do the following: + - Remove the generated git repository, `rm -rf .git` (this is important!) - Double check that the `pkg_name` is the same as the name of the directory @@ -110,28 +146,40 @@ You can also use `scarb fmt` to format all the Cairo programs. Every listing needs to have atleast integration tests: -- Integration tests are tests that deploy the contract and interact with the provided interface(s). They should be placed on a separate file/module named `tests.cairo`. At minimal make one test to deploy the contract. +- Integration tests are tests that deploy the contract and interact with the provided interface(s). At minimal make one test to deploy the contract. -- (Optional) Unit tests do not have to deploy the contract and use the interface(s). Unit tests can use mocked implementation or state to test only one specific feature. They should be placed on the same file as the example (and hidden in the book using ANCHOR). +- (Optional) Unit tests do not have to deploy the contract and use the interface(s). Unit tests can use mocked implementation or state to test only one specific feature. -The tests modules need to have the `#[cfg(test)]` flag. +Add your contract in a specific file, you can name it `contract.cairo` or anything else. You can also add other files if needed. -Add your contract in a different file, you can name it `contract.cairo` or anything else. You can also add other files if needed. +You should add the tests in the same file as the contract, using the `#[cfg(test)]` flag and a `tests` module. With the usage of ANCHOR, the tests will not be displayed in the book but can be optionally be displayed by using the `{{#rustdoc_include ...}}` syntax. Here's a sample `lib.cairo` file: ```cairo mod contract; +// any other modules you want +``` + +And in the `contract.cairo` file: + +```cairo +// ANCHOR: contract +// Write your contract here +// ANCHOR_END: contract #[cfg(test)] -mod tests; +mod tests { + // Write your tests for the contract here +} ``` -You can also use Starknet Foundry to write and run your tests. +You can use Starknet Foundry to write and run your tests. ### Use of anchor You can add delimiting comments to select part of the code in the book. + ```cairo file.cairo: @@ -144,11 +192,12 @@ c Then, in the markdown file, you can use the following syntax to include only the code between the delimiting comments: -```markdown - ```rust - {{#include ../../listings/path/to/listing/src/contract.cairo:anchor_name}} - \``` -``` +````markdown +````rust +{{#include ../../listings/path/to/listing/src/contract.cairo:anchor_name}} +\``` +```` +```` This will result in the following code being included in the book: @@ -156,8 +205,19 @@ This will result in the following code being included in the book: b ``` +Using `#rustdoc_include` you can have the same result, but users can expand the code in the book to see the whole file (used for showing tests): + +````markdown +````rust +{{#rustdoc_include ../../listings/path/to/listing/src/contract.cairo:anchor_name}} +\``` +```` +```` + ## Translations +> Translations efforts are currently on hold. If you are interested in helping out, please send a message in the telegram channel. + To work with translations, those are the steps to update the translated content: - Run a local server for the language you want to edit: `./translations.sh zh-cn` for instance. If no language is provided, the script will only extract translations from english. @@ -195,21 +255,21 @@ orientation. Examples of behavior that contributes to creating a positive environment include: -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members +- Using welcoming and inclusive language +- Being respectful of differing viewpoints and experiences +- Gracefully accepting constructive criticism +- Focusing on what is best for the community +- Showing empathy towards other community members Examples of unacceptable behavior by participants include: -* The use of sexualized language or imagery and unwelcome sexual attention or -advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic +- The use of sexualized language or imagery and unwelcome sexual attention or + advances +- Trolling, insulting/derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a +- Other conduct which could reasonably be considered inappropriate in a professional setting ### Our Responsibilities @@ -253,3 +313,5 @@ available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ + +Copyright (c) 2024 Nethermind diff --git a/LICENSE b/LICENSE index 006546af..1a461a21 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2023 Nethermind +Copyright (c) 2024 Nethermind Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/Scarb.lock b/Scarb.lock index c84915d1..ee2add0e 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -5,17 +5,11 @@ version = 1 name = "advanced_factory" version = "0.1.0" dependencies = [ - "alexandria_storage", "components", "crowdfunding", "snforge_std", ] -[[package]] -name = "alexandria_storage" -version = "0.3.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=800f5ad#800f5ad217847b5ded63c0302a444161766ee9d6" - [[package]] name = "bytearray" version = "0.1.0" @@ -117,12 +111,100 @@ dependencies = [ [[package]] name = "openzeppelin" -version = "0.14.0" -source = "git+https://github.com/OpenZeppelin/cairo-contracts.git?tag=v0.14.0#f091c4f51ddeb10297db984acae965328c5a4e5b" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:5dc87a71f0f5d045e02e5bc5ea7a9b0360bbbc3f388b846006280d266feac192" +dependencies = [ + "openzeppelin_merkle_tree", + "openzeppelin_presets", + "openzeppelin_security", + "openzeppelin_token", + "openzeppelin_upgrades", +] [[package]] -name = "scarb" -version = "0.1.0" +name = "openzeppelin_access" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:b9139449e53d715992b2d9e887c0c723d886419bee7ceb5561648c70bd6d3174" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_account" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:841bb881adbe98b64fee1dc1329f6e3fbabdfbd9fa65c66ffb54c55f13764bce" +dependencies = [ + "openzeppelin_introspection", + "openzeppelin_utils", +] + +[[package]] +name = "openzeppelin_governance" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:a9d9c983cfd4369e7bbb69433fb264edf376805ed873b1f70a287825a6bd1eaf" +dependencies = [ + "openzeppelin_access", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_introspection" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:312bc2e531f036480ad7392dbb31042c38d875ef9dbb5578ea8de5c05e35b7d8" + +[[package]] +name = "openzeppelin_merkle_tree" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:8e264c8d5f0591262a235a445b8ca78dd5580c251707b218b829b6b4d6f84a34" + +[[package]] +name = "openzeppelin_presets" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:7bc9db7563e434535ebd06c7598ba0f8494e791c5f80401389b356a3ebd65a08" +dependencies = [ + "openzeppelin_access", + "openzeppelin_account", + "openzeppelin_introspection", + "openzeppelin_token", + "openzeppelin_upgrades", +] + +[[package]] +name = "openzeppelin_security" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:b7497f5e55c6d9f1d7606ad0d12719e3c04765e266fb91fb72740e0af41d03f3" + +[[package]] +name = "openzeppelin_token" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:7074c23fbc300b3cccff1037264dfdbe976fb11ae42ce687f4a8ce469adc552a" +dependencies = [ + "openzeppelin_account", + "openzeppelin_governance", + "openzeppelin_introspection", +] + +[[package]] +name = "openzeppelin_upgrades" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:4de95a7fb8955e63711f34bb954676f6cef127b44b570c0535d7781297821b44" + +[[package]] +name = "openzeppelin_utils" +version = "0.16.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:a494aeb5f1371db7f22e922196aa41d1d1698877a766a838350c0b6ffe49fda2" [[package]] name = "simple_account" @@ -142,10 +224,20 @@ dependencies = [ "erc20", ] +[[package]] +name = "snforge_scarb_plugin" +version = "0.2.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:2e4ce3ebe3f49548bd26908391b5d78537a765d827df0d96c32aeb88941d0d67" + [[package]] name = "snforge_std" -version = "0.25.0" -source = "git+https://github.com/foundry-rs/starknet-foundry.git?tag=v0.25.0#5b366e24821e530fea97f11b211d220e8493fbea" +version = "0.30.0" +source = "registry+https://scarbs.xyz/" +checksum = "sha256:2f3c4846881813ac0f5d1460981249c9f5e2a6831e752beedf9b70975495b4ec" +dependencies = [ + "snforge_scarb_plugin", +] [[package]] name = "staking" @@ -162,10 +254,6 @@ version = "0.1.0" name = "store_using_packing" version = "0.1.0" -[[package]] -name = "storing_arrays" -version = "0.1.0" - [[package]] name = "storing_custom_types" version = "0.1.0" @@ -191,13 +279,6 @@ dependencies = [ name = "upgradeable_contract" version = "0.1.0" -[[package]] -name = "using_lists" -version = "0.1.0" -dependencies = [ - "alexandria_storage", -] - [[package]] name = "variables" version = "0.1.0" diff --git a/Scarb.toml b/Scarb.toml index 8df8fcb2..f8dd7cba 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -12,12 +12,12 @@ test = "$(git rev-parse --show-toplevel)/scripts/test_resolver.sh" [workspace.tool.snforge] [workspace.dependencies] -starknet = ">=2.6.4" -openzeppelin = { git = "https://github.com/OpenZeppelin/cairo-contracts.git", tag="v0.14.0" } +starknet = "2.8.2" +cairo_test = "2.8.2" +assert_macros = "2.8.2" +snforge_std = "0.30.0" +openzeppelin = "0.16.0" components = { path = "listings/applications/components" } -snforge_std = { git = "https://github.com/foundry-rs/starknet-foundry.git", tag = "v0.25.0" } -# The latest Alexandria release supports only Cairo v2.6.0, so using explicit rev that supports Cairo v2.6.3 -alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git", rev="800f5ad" } [workspace.package] description = "Collection of examples of how to use the Cairo programming language to create smart contracts on Starknet." @@ -27,8 +27,5 @@ license = "MIT" authors = ["julio4", "msaug"] version = "0.1.0" -[scripts] -test.workspace = true - [tool] snforge.workspace = true diff --git a/listings/advanced-concepts/ecdsa_verification/Scarb.toml b/listings/advanced-concepts/ecdsa_verification/Scarb.toml index 9c069f75..96ed1b58 100644 --- a/listings/advanced-concepts/ecdsa_verification/Scarb.toml +++ b/listings/advanced-concepts/ecdsa_verification/Scarb.toml @@ -1,12 +1,14 @@ [package] name = "ecdsa_verification" version.workspace = true -edition = '2023_11' - +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo b/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo index 8fd21af0..4fbbb6ff 100644 --- a/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo +++ b/listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo @@ -22,14 +22,8 @@ trait IVerifySignature { mod verifySignature { use super::IVerifySignature; use core::starknet::eth_address::EthAddress; - use starknet::get_caller_address; - use starknet::secp256_trait; - use starknet::secp256k1::{Secp256k1Point}; - use starknet::{SyscallResult, SyscallResultTrait}; - use starknet::secp256_trait::{ - Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key, - is_signature_entry_valid - }; + use starknet::secp256k1::Secp256k1Point; + use starknet::secp256_trait::{Signature, signature_from_vrs, recover_public_key,}; use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; #[storage] @@ -74,7 +68,8 @@ mod verifySignature { verify_eth_signature(:msg_hash, :signature, :eth_address); } - /// Recovers the public key from an Ethereum signature and verifies that it matches the given Ethereum address. + /// Recovers the public key from an Ethereum signature and verifies that it matches the + /// given Ethereum address. /// /// # Arguments /// @@ -98,10 +93,7 @@ mod verifySignature { #[cfg(test)] mod tests { - use starknet::secp256_trait::{ - Secp256Trait, Secp256PointTrait, Signature, signature_from_vrs, recover_public_key, - is_signature_entry_valid - }; + use starknet::secp256_trait::{Signature, signature_from_vrs, recover_public_key,}; use starknet::EthAddress; use starknet::secp256k1::{Secp256k1Point}; use starknet::eth_signature::{verify_eth_signature, public_key_point_to_eth_address}; @@ -121,14 +113,14 @@ mod tests { #[test] fn test_verify_eth_signature() { let (msg_hash, signature, eth_address) = get_message_and_signature(); - verify_eth_signature(:msg_hash, :signature, :eth_address); + verify_eth_signature(msg_hash, signature, eth_address); } #[test] fn test_secp256k1_recover_public_key() { let (msg_hash, signature, eth_address) = get_message_and_signature(); let public_key_point = recover_public_key::(msg_hash, signature).unwrap(); - let calculated_eth_address = public_key_point_to_eth_address(:public_key_point); + let calculated_eth_address = public_key_point_to_eth_address(public_key_point); assert_eq!(calculated_eth_address, eth_address); } } diff --git a/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml b/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml index 702fea43..9f3c6244 100644 --- a/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml +++ b/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "hash_solidity_compatible" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/advanced-concepts/hash_solidity_compatible/src/contract.cairo b/listings/advanced-concepts/hash_solidity_compatible/src/contract.cairo index 08e357f5..d46b5dcc 100644 --- a/listings/advanced-concepts/hash_solidity_compatible/src/contract.cairo +++ b/listings/advanced-concepts/hash_solidity_compatible/src/contract.cairo @@ -5,7 +5,7 @@ pub trait ISolidityHashExample { #[starknet::contract] pub mod SolidityHashExample { - use keccak::{keccak_u256s_be_inputs}; + use core::keccak::keccak_u256s_be_inputs; use core::integer; #[storage] diff --git a/listings/advanced-concepts/hash_solidity_compatible/src/tests.cairo b/listings/advanced-concepts/hash_solidity_compatible/src/tests.cairo index cae933bc..1f344672 100644 --- a/listings/advanced-concepts/hash_solidity_compatible/src/tests.cairo +++ b/listings/advanced-concepts/hash_solidity_compatible/src/tests.cairo @@ -1,10 +1,6 @@ mod tests { - use hash_solidity_compatible::{contract::{SolidityHashExample, ISolidityHashExample}}; - use starknet::{ - ContractAddress, get_contract_address, contract_address_const, - testing::{set_contract_address} - }; - // use starknet::syscalls::call_contract_syscall; + use hash_solidity_compatible::contract::{SolidityHashExample, ISolidityHashExample}; + use starknet::{contract_address_const, testing::set_contract_address}; fn setup() -> SolidityHashExample::ContractState { let mut state = SolidityHashExample::contract_state_for_testing(); @@ -14,7 +10,6 @@ mod tests { } #[test] - #[available_gas(2000000000)] fn get_same_hash_solidity() { let mut state = setup(); let mut array: Array = array![]; @@ -24,6 +19,6 @@ mod tests { 0xb10e2d527612073b26eecdfd717e6a320cf44b4afac2b0732d9fcbe2b7fa0cf6; let hash_received: u256 = state.hash_data(array.span()); - assert(hash_received == hash_expected, 'hash_received != hash_expected'); + assert_eq!(hash_received, hash_expected); } } diff --git a/listings/advanced-concepts/hash_trait/Scarb.toml b/listings/advanced-concepts/hash_trait/Scarb.toml index e810652f..44a44d27 100644 --- a/listings/advanced-concepts/hash_trait/Scarb.toml +++ b/listings/advanced-concepts/hash_trait/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "hash_trait" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/advanced-concepts/hash_trait/src/hash_trait.cairo b/listings/advanced-concepts/hash_trait/src/hash_trait.cairo index f09592f9..4dfb9994 100644 --- a/listings/advanced-concepts/hash_trait/src/hash_trait.cairo +++ b/listings/advanced-concepts/hash_trait/src/hash_trait.cairo @@ -11,6 +11,7 @@ pub trait IHashTrait { // ANCHOR: hash #[starknet::contract] pub mod HashTraits { + use starknet::storage::StoragePointerWriteAccess; use core::hash::{HashStateTrait, HashStateExTrait}; use core::{pedersen::PedersenTrait, poseidon::PoseidonTrait}; @@ -65,9 +66,6 @@ pub mod HashTraits { mod tests { use starknet::SyscallResultTrait; use super::{HashTraits, IHashTraitDispatcher, IHashTraitDispatcherTrait}; - - use core::hash::{HashStateTrait, HashStateExTrait}; - use core::{pedersen::PedersenTrait, poseidon::PoseidonTrait}; use starknet::syscalls::deploy_syscall; fn deploy() -> IHashTraitDispatcher { @@ -81,7 +79,6 @@ mod tests { #[test] - #[available_gas(20000000)] fn test_pedersen_hash() { let mut contract = deploy(); @@ -90,14 +87,10 @@ mod tests { let password = 'password.stark'; let test_hash = contract.save_user_with_pedersen(id, username, password); - assert( - test_hash == 0x6da4b4d0489989f5483d179643dafb3405b0e3b883a6c8efe5beb824ba9055a, - 'Incorrect hash output' - ); + assert_eq!(test_hash, 0x6da4b4d0489989f5483d179643dafb3405b0e3b883a6c8efe5beb824ba9055a); } #[test] - #[available_gas(20000000)] fn test_poseidon_hash() { let mut contract = deploy(); @@ -107,9 +100,6 @@ mod tests { let test_hash = contract.save_user_with_poseidon(id, username, password); - assert( - test_hash == 0x4d165e1d398ae4864854518d3c58c3d7a21ed9c1f8f3618fbb0031d208aab7b, - 'Incorrect hash output' - ); + assert_eq!(test_hash, 0x4d165e1d398ae4864854518d3c58c3d7a21ed9c1f8f3618fbb0031d208aab7b); } } diff --git a/listings/advanced-concepts/library_calls/Scarb.toml b/listings/advanced-concepts/library_calls/Scarb.toml index 0ea12255..e077de16 100644 --- a/listings/advanced-concepts/library_calls/Scarb.toml +++ b/listings/advanced-concepts/library_calls/Scarb.toml @@ -1,14 +1,15 @@ [package] name = "library_calls" version.workspace = true -edition = "2023_11" - -# See more keys and their definitions at https://docs.swmansion.com/scarb/docs/reference/manifest.html +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true -[[target.starknet-contract]] \ No newline at end of file +[[target.starknet-contract]] diff --git a/listings/advanced-concepts/library_calls/src/library_call.cairo b/listings/advanced-concepts/library_calls/src/library_call.cairo index a9f7cd56..93b7b6db 100644 --- a/listings/advanced-concepts/library_calls/src/library_call.cairo +++ b/listings/advanced-concepts/library_calls/src/library_call.cairo @@ -25,8 +25,8 @@ pub mod MathUtils { // contract B to make library call to the class of contract A #[starknet::contract] pub mod MathUtilsLibraryCall { - use starknet::{class_hash::class_hash_const, ContractAddress}; use super::{IMathUtilsDispatcherTrait, IMathUtilsLibraryDispatcher}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { diff --git a/listings/advanced-concepts/library_calls/src/tests.cairo b/listings/advanced-concepts/library_calls/src/tests.cairo index 1b9ecdf0..6458ef4d 100644 --- a/listings/advanced-concepts/library_calls/src/tests.cairo +++ b/listings/advanced-concepts/library_calls/src/tests.cairo @@ -1,12 +1,11 @@ mod tests { - use starknet::syscalls::{deploy_syscall}; + use starknet::syscalls::deploy_syscall; use starknet::SyscallResultTrait; use library_calls::library_call::{ MathUtils, MathUtilsLibraryCall, IMathUtilsDispatcher, IMathUtilsDispatcherTrait }; #[test] - #[available_gas(20000000)] fn test_library_dispatcher() { let math_utils_class_hash: starknet::ClassHash = MathUtils::TEST_CLASS_HASH .try_into() diff --git a/listings/advanced-concepts/simple_account/Scarb.toml b/listings/advanced-concepts/simple_account/Scarb.toml index f6fbac41..7454c359 100644 --- a/listings/advanced-concepts/simple_account/Scarb.toml +++ b/listings/advanced-concepts/simple_account/Scarb.toml @@ -1,8 +1,7 @@ [package] name = "simple_account" version.workspace = true -edition = '2023_11' - +edition = "2024_07" [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/simple_account/src/simple_account.cairo b/listings/advanced-concepts/simple_account/src/simple_account.cairo index 6de8b4ae..6de72c81 100644 --- a/listings/advanced-concepts/simple_account/src/simple_account.cairo +++ b/listings/advanced-concepts/simple_account/src/simple_account.cairo @@ -15,6 +15,7 @@ mod simpleAccount { use starknet::account::Call; use core::num::traits::Zero; use core::ecdsa::check_ecdsa_signature; + use starknet::storage::{StoragePointerWriteAccess, StoragePointerReadAccess}; // Implement SRC5 with openzeppelin use openzeppelin::account::interface; diff --git a/listings/advanced-concepts/store_using_packing/Scarb.toml b/listings/advanced-concepts/store_using_packing/Scarb.toml index 4854defc..2625aca3 100644 --- a/listings/advanced-concepts/store_using_packing/Scarb.toml +++ b/listings/advanced-concepts/store_using_packing/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "store_using_packing" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/advanced-concepts/store_using_packing/src/contract.cairo b/listings/advanced-concepts/store_using_packing/src/contract.cairo index 5f124ac2..8ef6b2db 100644 --- a/listings/advanced-concepts/store_using_packing/src/contract.cairo +++ b/listings/advanced-concepts/store_using_packing/src/contract.cairo @@ -12,6 +12,7 @@ pub trait ITime { #[starknet::contract] pub mod TimeContract { + use starknet::storage::{StoragePointerWriteAccess, StoragePointerReadAccess}; use super::Time; use starknet::storage_access::StorePacking; diff --git a/listings/advanced-concepts/store_using_packing/src/tests.cairo b/listings/advanced-concepts/store_using_packing/src/tests.cairo index 64a99b4e..eb888025 100644 --- a/listings/advanced-concepts/store_using_packing/src/tests.cairo +++ b/listings/advanced-concepts/store_using_packing/src/tests.cairo @@ -5,7 +5,6 @@ mod tests { use starknet::syscalls::deploy_syscall; #[test] - #[available_gas(20000000)] fn test_packing() { // Set up. let mut calldata: Array = array![]; @@ -21,7 +20,7 @@ mod tests { // Read the stored struct. let read_time: Time = contract.get(); - assert(read_time.hour == time.hour, 'Time.hour mismatch'); - assert(read_time.minute == time.minute, 'Time.minute mismatch'); + assert_eq!(read_time.hour, time.hour); + assert_eq!(read_time.minute, time.minute); } } diff --git a/listings/advanced-concepts/storing_arrays/.gitignore b/listings/advanced-concepts/storing_arrays/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/listings/advanced-concepts/storing_arrays/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/listings/advanced-concepts/storing_arrays/Scarb.lock b/listings/advanced-concepts/storing_arrays/Scarb.lock deleted file mode 100644 index 092d80f9..00000000 --- a/listings/advanced-concepts/storing_arrays/Scarb.lock +++ /dev/null @@ -1,6 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "storing_arrays" -version.workspace = true diff --git a/listings/advanced-concepts/storing_arrays/Scarb.toml b/listings/advanced-concepts/storing_arrays/Scarb.toml deleted file mode 100644 index 53ab4279..00000000 --- a/listings/advanced-concepts/storing_arrays/Scarb.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "storing_arrays" -version.workspace = true -edition = '2023_11' - -[dependencies] -starknet.workspace = true - -[scripts] -test.workspace = true - -[[target.starknet-contract]] diff --git a/listings/advanced-concepts/storing_arrays/src/contract.cairo b/listings/advanced-concepts/storing_arrays/src/contract.cairo deleted file mode 100644 index 991631be..00000000 --- a/listings/advanced-concepts/storing_arrays/src/contract.cairo +++ /dev/null @@ -1,96 +0,0 @@ -use starknet::SyscallResultTrait; -use starknet::{Store, SyscallResult}; -use starknet::storage_access::StorageBaseAddress; - -// ANCHOR: StorageAccessImpl -impl StoreFelt252Array of Store> { - fn read(address_domain: u32, base: StorageBaseAddress) -> SyscallResult> { - StoreFelt252Array::read_at_offset(address_domain, base, 0) - } - - fn write( - address_domain: u32, base: StorageBaseAddress, value: Array - ) -> SyscallResult<()> { - StoreFelt252Array::write_at_offset(address_domain, base, 0, value) - } - - fn read_at_offset( - address_domain: u32, base: StorageBaseAddress, mut offset: u8 - ) -> SyscallResult> { - let mut arr: Array = array![]; - - // Read the stored array's length. If the length is greater than 255, the read will fail. - let len: u8 = Store::::read_at_offset(address_domain, base, offset) - .expect('Storage Span too large'); - offset += 1; - - // Sequentially read all stored elements and append them to the array. - let exit = len + offset; - loop { - if offset >= exit { - break; - } - - let value = Store::::read_at_offset(address_domain, base, offset).unwrap(); - arr.append(value); - offset += Store::::size(); - }; - - // Return the array. - Result::Ok(arr) - } - - fn write_at_offset( - address_domain: u32, base: StorageBaseAddress, mut offset: u8, mut value: Array - ) -> SyscallResult<()> { - // Store the length of the array in the first storage slot. - let len: u8 = value.len().try_into().expect('Storage - Span too large'); - Store::::write_at_offset(address_domain, base, offset, len).unwrap(); - offset += 1; - - // Store the array elements sequentially - while let Option::Some(element) = value - .pop_front() { - Store::::write_at_offset(address_domain, base, offset, element).unwrap(); - offset += Store::::size(); - }; - - Result::Ok(()) - } - - fn size() -> u8 { - 255 * Store::::size() - } -} -// ANCHOR_END: StorageAccessImpl - -// ANCHOR: StoreArrayContract -#[starknet::interface] -pub trait IStoreArrayContract { - fn store_array(ref self: TContractState, arr: Array); - fn read_array(self: @TContractState) -> Array; -} - -#[starknet::contract] -pub mod StoreArrayContract { - use super::StoreFelt252Array; - - #[storage] - struct Storage { - arr: Array - } - - #[abi(embed_v0)] - impl StoreArrayImpl of super::IStoreArrayContract { - fn store_array(ref self: ContractState, arr: Array) { - self.arr.write(arr); - } - - fn read_array(self: @ContractState) -> Array { - self.arr.read() - } - } -} -// ANCHOR_END: StoreArrayContract - - diff --git a/listings/advanced-concepts/storing_arrays/src/lib.cairo b/listings/advanced-concepts/storing_arrays/src/lib.cairo deleted file mode 100644 index 11ada17a..00000000 --- a/listings/advanced-concepts/storing_arrays/src/lib.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod contract; - -#[cfg(test)] -mod tests; diff --git a/listings/advanced-concepts/storing_arrays/src/tests.cairo b/listings/advanced-concepts/storing_arrays/src/tests.cairo deleted file mode 100644 index 7c4093a5..00000000 --- a/listings/advanced-concepts/storing_arrays/src/tests.cairo +++ /dev/null @@ -1,62 +0,0 @@ -mod tests { - use starknet::SyscallResultTrait; - use storing_arrays::contract::{ - StoreArrayContract, IStoreArrayContractDispatcher, IStoreArrayContractDispatcherTrait - }; - use starknet::syscalls::deploy_syscall; - - #[test] - #[available_gas(20000000)] - fn test_array_storage() { - // Set up. - let mut calldata: Array = array![]; - let (address0, _) = deploy_syscall( - StoreArrayContract::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap_syscall(); - let mut contract = IStoreArrayContractDispatcher { contract_address: address0 }; - - // Store an array. - let mut array: Array = array![]; - array.append(1); - array.append(2); - contract.store_array(array); - - // Read the array. - let read_array = contract.read_array(); - assert(read_array.len() == 2, 'Array length mismatch'); - assert(*read_array[0] == 1, 'Array element mismatch'); - assert(*read_array[1] == 2, 'Array element mismatch'); - } - - #[test] - #[available_gas(20000000000)] - #[should_panic(expected: ('Storage - Span too large', 'ENTRYPOINT_FAILED'))] - fn test_array_storage_too_large() { - // Set up. - let mut calldata: Array = array![]; - let (address0, _) = deploy_syscall( - StoreArrayContract::TEST_CLASS_HASH.try_into().unwrap(), 0, calldata.span(), false - ) - .unwrap(); - let mut contract = IStoreArrayContractDispatcher { contract_address: address0 }; - - // Store an array. - let mut array: Array = array![]; - let mut i = 0; - loop { - if i == 256 { - break (); - } - array.append(i); - i += 1; - }; - contract.store_array(array); - - // Read the array. - let read_array = contract.read_array(); - assert(read_array.len() == 2, 'Array too large'); - assert(*read_array[0] == 1, 'Array element mismatch'); - assert(*read_array[1] == 2, 'Array element mismatch'); - } -} diff --git a/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml b/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml index 84d888e3..8bfd3839 100644 --- a/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml +++ b/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "struct_as_mapping_key" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/advanced-concepts/struct_as_mapping_key/src/contract.cairo b/listings/advanced-concepts/struct_as_mapping_key/src/contract.cairo index e3733d43..2708e396 100644 --- a/listings/advanced-concepts/struct_as_mapping_key/src/contract.cairo +++ b/listings/advanced-concepts/struct_as_mapping_key/src/contract.cairo @@ -13,12 +13,12 @@ pub trait IPetRegistry { #[starknet::contract] pub mod PetRegistry { - use core::hash::{HashStateTrait, Hash}; use super::Pet; + use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; #[storage] struct Storage { - registration_time: LegacyMap::, + registration_time: Map::, } #[abi(embed_v0)] diff --git a/listings/advanced-concepts/struct_as_mapping_key/src/test.cairo b/listings/advanced-concepts/struct_as_mapping_key/src/test.cairo index b78463ab..34ebdf26 100644 --- a/listings/advanced-concepts/struct_as_mapping_key/src/test.cairo +++ b/listings/advanced-concepts/struct_as_mapping_key/src/test.cairo @@ -10,7 +10,6 @@ mod tests { } #[test] - #[available_gas(20000000)] fn test_e2e() { // Set up. let mut calldata: Array = array![]; @@ -27,6 +26,6 @@ mod tests { // Read the array. let registration_date = contract.get_registration_date(pet); - assert(registration_date == 1234, 'registration_date'); + assert_eq!(registration_date, 1234); } } diff --git a/listings/advanced-concepts/using_lists/.gitignore b/listings/advanced-concepts/using_lists/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/listings/advanced-concepts/using_lists/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/listings/advanced-concepts/using_lists/Scarb.lock b/listings/advanced-concepts/using_lists/Scarb.lock deleted file mode 100644 index 924a0297..00000000 --- a/listings/advanced-concepts/using_lists/Scarb.lock +++ /dev/null @@ -1,14 +0,0 @@ -# Code generated by scarb DO NOT EDIT. -version = 1 - -[[package]] -name = "alexandria_storage" -version = "0.2.0" -source = "git+https://github.com/keep-starknet-strange/alexandria.git?rev=ae1d514#ae1d5149ff601a7ac5b39edc867d33ebd83d7f4f" - -[[package]] -name = "using_lists" -version.workspace = true -dependencies = [ - "alexandria_storage", -] diff --git a/listings/advanced-concepts/using_lists/Scarb.toml b/listings/advanced-concepts/using_lists/Scarb.toml deleted file mode 100644 index 20fc9020..00000000 --- a/listings/advanced-concepts/using_lists/Scarb.toml +++ /dev/null @@ -1,13 +0,0 @@ -[package] -name = "using_lists" -version.workspace = true -edition = '2023_11' - -[dependencies] -starknet.workspace = true -alexandria_storage.workspace = true - -[scripts] -test.workspace = true - -[[target.starknet-contract]] diff --git a/listings/advanced-concepts/using_lists/src/contract.cairo b/listings/advanced-concepts/using_lists/src/contract.cairo deleted file mode 100644 index 9ee274db..00000000 --- a/listings/advanced-concepts/using_lists/src/contract.cairo +++ /dev/null @@ -1,72 +0,0 @@ -#[derive(Copy, Drop, Serde, starknet::Store)] -pub struct Task { - pub description: felt252, - pub status: felt252 -} - -#[starknet::interface] -pub trait IListExample { - fn add_in_amount(ref self: TContractState, number: u128); - fn add_in_task(ref self: TContractState, description: felt252, status: felt252); - fn is_empty_list(self: @TContractState) -> bool; - fn list_length(self: @TContractState) -> u32; - fn get_from_index(self: @TContractState, index: u32) -> u128; - fn set_from_index(ref self: TContractState, index: u32, number: u128); - fn pop_front_list(ref self: TContractState); - fn array_conversion(self: @TContractState) -> Array; -} - -#[starknet::contract] -pub mod ListExample { - use super::Task; - use alexandria_storage::list::{List, ListTrait}; - - #[storage] - pub struct Storage { - amounts: List, - tasks: List - } - - #[abi(embed_v0)] - impl ListExample of super::IListExample { - fn add_in_amount(ref self: ContractState, number: u128) { - let mut current_amount_list = self.amounts.read(); - current_amount_list.append(number).unwrap(); - } - - fn add_in_task(ref self: ContractState, description: felt252, status: felt252) { - let new_task = Task { description: description, status: status }; - let mut current_tasks_list = self.tasks.read(); - current_tasks_list.append(new_task).unwrap(); - } - - fn is_empty_list(self: @ContractState) -> bool { - let mut current_amount_list = self.amounts.read(); - current_amount_list.is_empty() - } - - fn list_length(self: @ContractState) -> u32 { - let mut current_amount_list = self.amounts.read(); - current_amount_list.len() - } - - fn get_from_index(self: @ContractState, index: u32) -> u128 { - self.amounts.read()[index] - } - - fn set_from_index(ref self: ContractState, index: u32, number: u128) { - let mut current_amount_list = self.amounts.read(); - current_amount_list.set(index, number).unwrap(); - } - - fn pop_front_list(ref self: ContractState) { - let mut current_amount_list = self.amounts.read(); - current_amount_list.pop_front().unwrap().unwrap(); - } - - fn array_conversion(self: @ContractState) -> Array { - let mut current_amount_list = self.amounts.read(); - current_amount_list.array().unwrap() - } - } -} diff --git a/listings/advanced-concepts/using_lists/src/lib.cairo b/listings/advanced-concepts/using_lists/src/lib.cairo deleted file mode 100644 index 11ada17a..00000000 --- a/listings/advanced-concepts/using_lists/src/lib.cairo +++ /dev/null @@ -1,4 +0,0 @@ -mod contract; - -#[cfg(test)] -mod tests; diff --git a/listings/advanced-concepts/using_lists/src/tests.cairo b/listings/advanced-concepts/using_lists/src/tests.cairo deleted file mode 100644 index 98f667c3..00000000 --- a/listings/advanced-concepts/using_lists/src/tests.cairo +++ /dev/null @@ -1,101 +0,0 @@ -use using_lists::contract::IListExample; -use using_lists::contract::{Task, ListExample}; -use using_lists::contract::ListExample::{ - amountsContractMemberStateTrait, tasksContractMemberStateTrait -}; - -fn STATE() -> ListExample::ContractState { - ListExample::contract_state_for_testing() -} - -#[test] -#[available_gas(2000000)] -fn test_add_in_amount() { - let mut state = STATE(); - state.add_in_amount(200); - assert(state.amounts.read()[0] == 200, 'should be 200'); -} - -#[test] -#[available_gas(2000000)] -fn test_add_in_task() { - let mut state = STATE(); - state.add_in_task('test_description', 'test_status'); - let current_task: Task = state.tasks.read()[0]; - assert(current_task.description == 'test_description', 'should be test_description'); - assert(current_task.status == 'test_status', 'should be test_status'); -} - -#[test] -#[available_gas(2000000)] -fn test_is_empty_list() { - let mut state = STATE(); - - let pre_addition = state.is_empty_list(); - assert(pre_addition == true, 'should be true'); - - state.add_in_amount(200); - let post_addition = state.is_empty_list(); - assert(post_addition == false, 'should be false'); -} - -#[test] -#[available_gas(2000000)] -fn test_list_length() { - let mut state = STATE(); - - let pre_addition = state.list_length(); - assert(pre_addition == 0, 'should be zero'); - - state.add_in_amount(200); - let post_addition = state.list_length(); - assert(post_addition == 1, 'should be 1'); -} - -#[test] -#[available_gas(2000000)] -fn test_get_from_index() { - let mut state = STATE(); - state.add_in_amount(200); - let output = state.get_from_index(0); - assert(output == 200, 'should be 200'); -} - -#[test] -#[available_gas(2000000)] -fn test_set_from_index() { - let mut state = STATE(); - state.add_in_amount(200); - state.set_from_index(0, 400); - assert(state.amounts.read()[0] == 400, 'should be 400'); -} - -#[test] -#[available_gas(2000000)] -fn test_pop_front_list() { - let mut state = STATE(); - - state.add_in_amount(200); - let pre_pop_front = state.list_length(); - assert(pre_pop_front == 1, 'should be 1'); - - state.pop_front_list(); - let post_pop_front = state.list_length(); - assert(post_pop_front == 0, 'should be zero'); -} - -#[test] -#[available_gas(2000000)] -fn test_array_conversion() { - let mut ideal_array = ArrayTrait::::new(); - ideal_array.append(200); - ideal_array.append(400); - - let mut state = STATE(); - - state.add_in_amount(200); - state.add_in_amount(400); - let output: Array = state.array_conversion(); - - assert(output == ideal_array, 'should be equal'); -} diff --git a/listings/advanced-concepts/write_to_any_slot/Scarb.toml b/listings/advanced-concepts/write_to_any_slot/Scarb.toml index 7ac53e05..cbad5243 100644 --- a/listings/advanced-concepts/write_to_any_slot/Scarb.toml +++ b/listings/advanced-concepts/write_to_any_slot/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "write_to_any_slot" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/advanced-concepts/write_to_any_slot/src/contract.cairo b/listings/advanced-concepts/write_to_any_slot/src/contract.cairo index 0a08a21b..1e7d4922 100644 --- a/listings/advanced-concepts/write_to_any_slot/src/contract.cairo +++ b/listings/advanced-concepts/write_to_any_slot/src/contract.cairo @@ -35,7 +35,8 @@ pub mod WriteToAnySlot { data.append(variable_name); let hashed_name: felt252 = poseidon_hash_span(data.span()); let MASK_250: u256 = 0x03ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff; - // By taking the 250 least significant bits of the hash output, we get a valid 250bits storage address. + // By taking the 250 least significant bits of the hash output, we get a valid 250bits + // storage address. let result: felt252 = (hashed_name.into() & MASK_250).try_into().unwrap(); let result: StorageAddress = result.try_into().unwrap(); result diff --git a/listings/advanced-concepts/write_to_any_slot/src/tests.cairo b/listings/advanced-concepts/write_to_any_slot/src/tests.cairo index 371be462..6428a6eb 100644 --- a/listings/advanced-concepts/write_to_any_slot/src/tests.cairo +++ b/listings/advanced-concepts/write_to_any_slot/src/tests.cairo @@ -7,13 +7,11 @@ trait IWriteToAnySlot { } mod tests { - use super::WriteToAnySlot::get_address_from_name; use super::WriteToAnySlot; use super::{IWriteToAnySlotDispatcher, IWriteToAnySlotDispatcherTrait}; use starknet::syscalls::deploy_syscall; #[test] - #[available_gas(2000000000)] fn test_read_write() { // Set up. let mut calldata: Array = array![]; @@ -29,7 +27,7 @@ mod tests { // Read from slot. let read_value = contract.read_slot(); - assert(read_value == value, 'wrong value read'); + assert_eq!(read_value, value); } } diff --git a/listings/applications/advanced_factory/Scarb.toml b/listings/applications/advanced_factory/Scarb.toml index 5935e01f..825a4654 100644 --- a/listings/applications/advanced_factory/Scarb.toml +++ b/listings/applications/advanced_factory/Scarb.toml @@ -1,15 +1,17 @@ [package] name = "advanced_factory" version.workspace = true -edition = "2023_11" +edition = "2024_07" [dependencies] starknet.workspace = true components.workspace = true -alexandria_storage.workspace = true snforge_std.workspace = true crowdfunding = { path = "../crowdfunding" } +[dev-dependencies] +assert_macros.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/advanced_factory/src/contract.cairo b/listings/applications/advanced_factory/src/contract.cairo index d3f03432..4c6b4c89 100644 --- a/listings/applications/advanced_factory/src/contract.cairo +++ b/listings/applications/advanced_factory/src/contract.cairo @@ -23,12 +23,14 @@ pub trait ICampaignFactory { pub mod CampaignFactory { use core::num::traits::Zero; use starknet::{ - ContractAddress, ClassHash, SyscallResultTrait, syscalls::deploy_syscall, - get_caller_address, get_contract_address + ContractAddress, ClassHash, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address }; - use alexandria_storage::list::{List, ListTrait}; use crowdfunding::campaign::{ICampaignDispatcher, ICampaignDispatcherTrait}; use components::ownable::ownable_component; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; component!(path: ownable_component, storage: ownable, event: OwnableEvent); @@ -41,7 +43,7 @@ pub mod CampaignFactory { #[substorage(v0)] ownable: ownable_component::Storage, /// Store all of the created campaign instances' addresses and thei class hashes - campaigns: LegacyMap<(ContractAddress, ContractAddress), ClassHash>, + campaigns: Map<(ContractAddress, ContractAddress), ClassHash>, /// Store the class hash of the contract to deploy campaign_class_hash: ClassHash, } diff --git a/listings/applications/advanced_factory/src/tests.cairo b/listings/applications/advanced_factory/src/tests.cairo index 437a9f87..759be3d0 100644 --- a/listings/applications/advanced_factory/src/tests.cairo +++ b/listings/applications/advanced_factory/src/tests.cairo @@ -3,26 +3,24 @@ use core::result::ResultTrait; use advanced_factory::contract::{ CampaignFactory, ICampaignFactoryDispatcher, ICampaignFactoryDispatcherTrait }; -use starknet::{ - ContractAddress, ClassHash, get_block_timestamp, contract_address_const, get_caller_address -}; +use crowdfunding::campaign::Campaign; +use starknet::{ClassHash, get_block_timestamp, contract_address_const}; use snforge_std::{ - declare, ContractClass, ContractClassTrait, start_cheat_caller_address, - stop_cheat_caller_address, spy_events, SpyOn, EventSpy, EventAssertions, get_class_hash + declare, start_cheat_caller_address, stop_cheat_caller_address, spy_events, DeclareResultTrait, + ContractClassTrait, get_class_hash, EventSpyAssertionsTrait }; // Define a goal contract to deploy -use crowdfunding::campaign::{Campaign, ICampaignDispatcher, ICampaignDispatcherTrait}; +use crowdfunding::campaign::{ICampaignDispatcher, ICampaignDispatcherTrait}; use components::ownable::{IOwnableDispatcher, IOwnableDispatcherTrait}; - /// Deploy a campaign factory contract with the provided campaign class hash fn deploy_factory_with(campaign_class_hash: ClassHash) -> ICampaignFactoryDispatcher { let mut constructor_calldata: @Array:: = @array![campaign_class_hash.into()]; - let contract = declare("CampaignFactory").unwrap(); + let contract = declare("CampaignFactory").unwrap().contract_class(); let contract_address = contract.precalculate_address(constructor_calldata); - let factory_owner: ContractAddress = contract_address_const::<'factory_owner'>(); + let factory_owner = contract_address_const::<'factory_owner'>(); start_cheat_caller_address(contract_address, factory_owner); contract.deploy(constructor_calldata).unwrap(); @@ -34,18 +32,18 @@ fn deploy_factory_with(campaign_class_hash: ClassHash) -> ICampaignFactoryDispat /// Deploy a campaign factory contract with default campaign class hash fn deploy_factory() -> ICampaignFactoryDispatcher { - let campaign_class_hash = declare("Campaign").unwrap().class_hash; - deploy_factory_with(campaign_class_hash) + let campaign = declare("Campaign").unwrap().contract_class(); + deploy_factory_with(*campaign.class_hash) } #[test] fn test_deploy_factory() { - let campaign_class_hash = declare("Campaign").unwrap().class_hash; - let factory = deploy_factory_with(campaign_class_hash); + let campaign = declare("Campaign").unwrap().contract_class(); + let factory = deploy_factory_with(*campaign.class_hash); - assert_eq!(factory.get_campaign_class_hash(), campaign_class_hash); + assert_eq!(factory.get_campaign_class_hash(), *campaign.class_hash); - let factory_owner: ContractAddress = contract_address_const::<'factory_owner'>(); + let factory_owner = contract_address_const::<'factory_owner'>(); let factory_ownable = IOwnableDispatcher { contract_address: factory.contract_address }; assert_eq!(factory_ownable.owner(), factory_owner); } @@ -54,9 +52,9 @@ fn test_deploy_factory() { fn test_create_campaign() { let factory = deploy_factory(); - let mut spy = spy_events(SpyOn::One(factory.contract_address)); + let mut spy = spy_events(); - let campaign_creator: ContractAddress = contract_address_const::<'campaign_creator'>(); + let campaign_creator = contract_address_const::<'campaign_creator'>(); start_cheat_caller_address(factory.contract_address, campaign_creator); let title: ByteArray = "New campaign"; @@ -104,7 +102,7 @@ fn test_create_campaign() { fn test_uprade_campaign_class_hash() { let factory = deploy_factory(); let old_class_hash = factory.get_campaign_class_hash(); - let new_class_hash = declare("MockContract").unwrap().class_hash; + let new_class_hash = *declare("MockContract").unwrap().contract_class().class_hash; let token = contract_address_const::<'token'>(); @@ -133,9 +131,7 @@ fn test_uprade_campaign_class_hash() { assert_eq!(old_class_hash, get_class_hash(active_campaign)); // update the factory's campaign class hash value - let mut spy = spy_events( - SpyOn::Multiple(array![factory.contract_address, pending_campaign, active_campaign]) - ); + let mut spy = spy_events(); let factory_owner = contract_address_const::<'factory_owner'>(); start_cheat_caller_address(factory.contract_address, factory_owner); diff --git a/listings/applications/components/Scarb.toml b/listings/applications/components/Scarb.toml index 3e1f13a1..afa1fd51 100644 --- a/listings/applications/components/Scarb.toml +++ b/listings/applications/components/Scarb.toml @@ -1,13 +1,16 @@ [package] name = "components" version.workspace = true -edition = '2023_11' +edition = "2024_07" [lib] [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/components/src/countable.cairo b/listings/applications/components/src/countable.cairo index 6ca26c63..141fe06d 100644 --- a/listings/applications/components/src/countable.cairo +++ b/listings/applications/components/src/countable.cairo @@ -7,8 +7,10 @@ pub trait ICountable { #[starknet::component] pub mod countable_component { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] - struct Storage { + pub struct Storage { countable_value: u32, } diff --git a/listings/applications/components/src/others/switch_collision.cairo b/listings/applications/components/src/others/switch_collision.cairo index 58b2973f..ee34f1e1 100644 --- a/listings/applications/components/src/others/switch_collision.cairo +++ b/listings/applications/components/src/others/switch_collision.cairo @@ -9,6 +9,7 @@ pub trait ISwitchCollision { #[starknet::contract] pub mod SwitchCollisionContract { use components::switchable::switchable_component; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; component!(path: switchable_component, storage: switch, event: SwitchableEvent); @@ -50,12 +51,10 @@ pub mod SwitchCollisionContract { #[cfg(test)] mod switch_collision_tests { - use components::switchable::switchable_component::SwitchableInternalTrait; - use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait}; + use components::switchable::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; use super::{ SwitchCollisionContract, ISwitchCollisionDispatcher, ISwitchCollisionDispatcherTrait }; - use starknet::storage::StorageMemberAccessTrait; use starknet::SyscallResultTrait; use starknet::syscalls::deploy_syscall; @@ -89,5 +88,5 @@ mod switch_collision_tests { contract.set(false); assert_eq!(contract.get(), contract_iswitch.is_on()); } -// ANCHOR_END: collision + // ANCHOR_END: collision } diff --git a/listings/applications/components/src/ownable.cairo b/listings/applications/components/src/ownable.cairo index e827c741..45d8457c 100644 --- a/listings/applications/components/src/ownable.cairo +++ b/listings/applications/components/src/ownable.cairo @@ -19,9 +19,10 @@ pub mod ownable_component { use super::Errors; use starknet::{ContractAddress, get_caller_address}; use core::num::traits::Zero; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] - struct Storage { + pub struct Storage { ownable_owner: ContractAddress, } @@ -99,7 +100,7 @@ pub mod ownable_component { // ANCHOR: contract #[starknet::contract] pub mod OwnedContract { - use super::{IOwnable, ownable_component, ownable_component::OwnableInternalTrait}; + use super::{ownable_component, ownable_component::OwnableInternalTrait}; component!(path: ownable_component, storage: ownable, event: OwnableEvent); @@ -128,12 +129,11 @@ pub mod OwnedContract { #[cfg(test)] mod test { use super::OwnedContract; - use super::ownable_component::{Event, OwnershipRenouncedEvent, OwnershipTransferredEvent}; + use super::ownable_component::{OwnershipRenouncedEvent, OwnershipTransferredEvent}; use super::{IOwnableDispatcher, IOwnableDispatcherTrait}; - use super::Errors; use starknet::ContractAddress; use starknet::{syscalls::deploy_syscall, SyscallResultTrait, contract_address_const}; - use starknet::testing::{set_caller_address, set_contract_address}; + use starknet::testing::{set_contract_address}; use core::num::traits::Zero; fn deploy() -> (IOwnableDispatcher, ContractAddress) { diff --git a/listings/applications/components/src/switchable.cairo b/listings/applications/components/src/switchable.cairo index e58096df..75fa0555 100644 --- a/listings/applications/components/src/switchable.cairo +++ b/listings/applications/components/src/switchable.cairo @@ -9,8 +9,10 @@ pub trait ISwitchable { #[starknet::component] pub mod switchable_component { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] - struct Storage { + pub struct Storage { switchable_value: bool, } @@ -85,9 +87,9 @@ pub mod SwitchContract { #[cfg(test)] mod test { use super::SwitchContract; // Used as a mock contract - use super::switchable_component::{Event, SwitchEvent}; + use super::switchable_component::SwitchEvent; use super::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; - use starknet::{syscalls::deploy_syscall, contract_address_const, ContractAddress}; + use starknet::{syscalls::deploy_syscall, ContractAddress}; use starknet::SyscallResultTrait; fn deploy() -> (ISwitchableDispatcher, ContractAddress) { diff --git a/listings/applications/components_dependencies/Scarb.toml b/listings/applications/components_dependencies/Scarb.toml index 4864600d..27d39111 100644 --- a/listings/applications/components_dependencies/Scarb.toml +++ b/listings/applications/components_dependencies/Scarb.toml @@ -1,12 +1,15 @@ [package] name = "components_dependencies" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true components = { path = "../components" } +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/components_dependencies/src/contract_countable.cairo b/listings/applications/components_dependencies/src/contract_countable.cairo index fd813427..bb587b36 100644 --- a/listings/applications/components_dependencies/src/contract_countable.cairo +++ b/listings/applications/components_dependencies/src/contract_countable.cairo @@ -1,6 +1,7 @@ // ANCHOR: contract #[starknet::contract] mod CountableContract { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components_dependencies::countable_dep_switch::countable_component; use components::switchable::ISwitchable; @@ -44,10 +45,9 @@ mod CountableContract { #[cfg(test)] mod tests { use super::CountableContract; - use components::countable::{ICountable, ICountableDispatcher, ICountableDispatcherTrait}; - use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait}; + use components::countable::{ICountableDispatcher, ICountableDispatcherTrait}; + use components::switchable::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; - use starknet::storage::StorageMemberAccessTrait; use starknet::SyscallResultTrait; use starknet::syscalls::deploy_syscall; @@ -61,38 +61,34 @@ mod tests { } #[test] - #[available_gas(2000000)] fn test_init() { let (mut counter, mut switch) = deploy(); - assert(counter.get() == 0, 'Counter != 0'); - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(counter.get(), 0); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(2000000)] fn test_increment_switch_off() { let (mut counter, mut switch) = deploy(); counter.increment(); - assert(counter.get() == 0, 'Counter incremented'); - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(counter.get(), 0); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(2000000)] fn test_increment_switch_on() { let (mut counter, mut switch) = deploy(); switch.switch(); - assert(switch.is_on() == true, 'Switch != true'); + assert_eq!(switch.is_on(), true); counter.increment(); - assert(counter.get() == 1, 'Counter did not increment'); + assert_eq!(counter.get(), 1); } #[test] - #[available_gas(2000000)] fn test_increment_multiple_switches() { let (mut counter, mut switch) = deploy(); @@ -101,7 +97,7 @@ mod tests { counter.increment(); counter.increment(); counter.increment(); - assert(counter.get() == 3, 'Counter did not increment'); + assert_eq!(counter.get(), 3); switch.switch(); counter.increment(); @@ -113,6 +109,6 @@ mod tests { counter.increment(); counter.increment(); counter.increment(); - assert(counter.get() == 6, 'Counter did not increment'); + assert_eq!(counter.get(), 6); } } diff --git a/listings/applications/components_dependencies/src/contract_countable_switchable.cairo b/listings/applications/components_dependencies/src/contract_countable_switchable.cairo index 9ef201fa..fdf8df55 100644 --- a/listings/applications/components_dependencies/src/contract_countable_switchable.cairo +++ b/listings/applications/components_dependencies/src/contract_countable_switchable.cairo @@ -38,10 +38,9 @@ mod CountableContract { #[cfg(test)] mod tests { use super::CountableContract; - use components::countable::{ICountable, ICountableDispatcher, ICountableDispatcherTrait}; - use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait}; + use components::countable::{ICountableDispatcher, ICountableDispatcherTrait}; + use components::switchable::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; - use starknet::storage::StorageMemberAccessTrait; use starknet::SyscallResultTrait; use starknet::syscalls::deploy_syscall; @@ -55,38 +54,34 @@ mod tests { } #[test] - #[available_gas(2000000)] fn test_init() { let (mut counter, mut switch) = deploy(); - assert(counter.get() == 0, 'Counter != 0'); - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(counter.get(), 0); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(2000000)] fn test_increment_switch_off() { let (mut counter, mut switch) = deploy(); counter.increment(); - assert(counter.get() == 0, 'Counter incremented'); - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(counter.get(), 0); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(2000000)] fn test_increment_switch_on() { let (mut counter, mut switch) = deploy(); switch.switch(); - assert(switch.is_on() == true, 'Switch != true'); + assert_eq!(switch.is_on(), true); counter.increment(); - assert(counter.get() == 1, 'Counter did not increment'); + assert_eq!(counter.get(), 1); } #[test] - #[available_gas(3000000)] fn test_increment_multiple_switches() { let (mut counter, mut switch) = deploy(); @@ -95,7 +90,7 @@ mod tests { counter.increment(); counter.increment(); counter.increment(); - assert(counter.get() == 3, 'Counter did not increment'); + assert_eq!(counter.get(), 3); switch.switch(); counter.increment(); @@ -107,6 +102,6 @@ mod tests { counter.increment(); counter.increment(); counter.increment(); - assert(counter.get() == 6, 'Counter did not increment'); + assert_eq!(counter.get(), 6); } } diff --git a/listings/applications/components_dependencies/src/contract_countable_switchable_internal.cairo b/listings/applications/components_dependencies/src/contract_countable_switchable_internal.cairo index e52f3cb1..bfc64933 100644 --- a/listings/applications/components_dependencies/src/contract_countable_switchable_internal.cairo +++ b/listings/applications/components_dependencies/src/contract_countable_switchable_internal.cairo @@ -38,10 +38,9 @@ pub mod CountableContract { #[cfg(test)] mod tests { use super::CountableContract; - use components::countable::{ICountable, ICountableDispatcher, ICountableDispatcherTrait}; - use components::switchable::{ISwitchable, ISwitchableDispatcher, ISwitchableDispatcherTrait}; + use components::countable::{ICountableDispatcher, ICountableDispatcherTrait}; + use components::switchable::{ISwitchableDispatcher, ISwitchableDispatcherTrait}; - use starknet::storage::StorageMemberAccessTrait; use starknet::SyscallResultTrait; use starknet::syscalls::deploy_syscall; @@ -55,41 +54,37 @@ mod tests { } #[test] - #[available_gas(2000000)] fn test_init() { let (mut counter, mut switch) = deploy(); - assert(counter.get() == 0, 'Counter != 0'); - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(counter.get(), 0); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(2000000)] fn test_increment_switch_off() { let (mut counter, mut switch) = deploy(); counter.increment(); - assert(counter.get() == 0, 'Counter incremented'); - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(counter.get(), 0); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(2000000)] fn test_increment_switch_on() { let (mut counter, mut switch) = deploy(); switch.switch(); - assert(switch.is_on() == true, 'Switch != true'); + assert_eq!(switch.is_on(), true); counter.increment(); - assert(counter.get() == 1, 'Counter did not increment'); + assert_eq!(counter.get(), 1); // The counter turned the switch off. - assert(switch.is_on() == false, 'Switch != false'); + assert_eq!(switch.is_on(), false); } #[test] - #[available_gas(3000000)] fn test_increment_multiple_switches() { let (mut counter, mut switch) = deploy(); @@ -98,13 +93,13 @@ mod tests { counter.increment(); counter.increment(); // off counter.increment(); // off - assert(counter.get() == 1, 'Counter did not increment'); + assert_eq!(counter.get(), 1); switch.switch(); counter.increment(); switch.switch(); counter.increment(); counter.increment(); - assert(counter.get() == 3, 'Counter did not increment'); + assert_eq!(counter.get(), 3); } } diff --git a/listings/applications/components_dependencies/src/countable_dep_switch.cairo b/listings/applications/components_dependencies/src/countable_dep_switch.cairo index 762470e5..4cc2a1a6 100644 --- a/listings/applications/components_dependencies/src/countable_dep_switch.cairo +++ b/listings/applications/components_dependencies/src/countable_dep_switch.cairo @@ -1,11 +1,12 @@ // ANCHOR: contract #[starknet::component] pub mod countable_component { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components::countable::ICountable; use components::switchable::ISwitchable; #[storage] - struct Storage { + pub struct Storage { countable_value: u32, } @@ -24,7 +25,7 @@ pub mod countable_component { } } } -// ANCHOR_END: impl + // ANCHOR_END: impl } //ANCHOR_END: contract diff --git a/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo b/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo index bfa5e020..b5f47720 100644 --- a/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo +++ b/listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo @@ -1,6 +1,7 @@ //ANCHOR: contract #[starknet::component] pub mod countable_component { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components::countable::ICountable; use components::switchable::ISwitchable; @@ -9,7 +10,7 @@ pub mod countable_component { use switchable_component::{SwitchableInternalImpl, SwitchableInternalTrait}; #[storage] - struct Storage { + pub struct Storage { countable_value: u32, } diff --git a/listings/applications/constant_product_amm/Scarb.toml b/listings/applications/constant_product_amm/Scarb.toml index 17a77276..4a798451 100644 --- a/listings/applications/constant_product_amm/Scarb.toml +++ b/listings/applications/constant_product_amm/Scarb.toml @@ -1,12 +1,15 @@ [package] name = "constant_product_amm" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true openzeppelin.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/constant_product_amm/src/contracts.cairo b/listings/applications/constant_product_amm/src/contracts.cairo index 5ac44d74..9b6ebfa4 100644 --- a/listings/applications/constant_product_amm/src/contracts.cairo +++ b/listings/applications/constant_product_amm/src/contracts.cairo @@ -10,11 +10,11 @@ pub trait IConstantProductAmm { #[starknet::contract] pub mod ConstantProductAmm { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; - use starknet::{ - ContractAddress, get_caller_address, get_contract_address, contract_address_const - }; - use core::integer::u256_sqrt; + use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; + use core::num::traits::Sqrt; #[storage] struct Storage { @@ -23,7 +23,7 @@ pub mod ConstantProductAmm { reserve0: u256, reserve1: u256, total_supply: u256, - balance_of: LegacyMap::, + balance_of: Map::, // Fee 0 - 1000 (0% - 100%, 1 decimal places) // E.g. 3 = 0.3% fee: u16, @@ -193,7 +193,7 @@ pub mod ConstantProductAmm { let total_supply = self.total_supply.read(); let shares = if (total_supply == 0) { - u256_sqrt(amount0 * amount1).into() + (amount0 * amount1).sqrt().into() } else { PrivateFunctions::min( amount0 * total_supply / reserve0, amount1 * total_supply / reserve1 diff --git a/listings/applications/constant_product_amm/src/tests.cairo b/listings/applications/constant_product_amm/src/tests.cairo index a94d111c..7a87e616 100644 --- a/listings/applications/constant_product_amm/src/tests.cairo +++ b/listings/applications/constant_product_amm/src/tests.cairo @@ -38,7 +38,8 @@ pub mod ERC20Token { // Wait for OZ #953 fix // mod tests { // use super::ERC20Token; -// use openzeppelin::token::erc20::{interface::IERC20Dispatcher, interface::IERC20DispatcherTrait}; +// use openzeppelin::token::erc20::{interface::IERC20Dispatcher, +// interface::IERC20DispatcherTrait}; // use openzeppelin::tests::utils; // use openzeppelin::utils::serde::SerializedAppend; @@ -93,7 +94,8 @@ pub mod ERC20Token { // .unwrap(); // // Or with OpenZeppelin helper: // // let contract_address = utils::deploy(ConstantProductAmm::TEST_CLASS_HASH, calldata); -// Deployment { contract: IConstantProductAmmDispatcher { contract_address }, token0, token1 } +// Deployment { contract: IConstantProductAmmDispatcher { contract_address }, token0, token1 +// } // } // fn add_liquidity(deploy: Deployment, amount: u256) -> u256 { diff --git a/listings/applications/crowdfunding/Scarb.toml b/listings/applications/crowdfunding/Scarb.toml index 074a2713..744ae4d2 100644 --- a/listings/applications/crowdfunding/Scarb.toml +++ b/listings/applications/crowdfunding/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "crowdfunding" version.workspace = true -edition = "2023_11" +edition = "2024_07" [lib] @@ -11,9 +11,11 @@ openzeppelin.workspace = true components.workspace = true snforge_std.workspace = true +[dev-dependencies] +assert_macros.workspace = true + [scripts] test.workspace = true [[target.starknet-contract]] -casm = true -build-external-contracts = ["openzeppelin::presets::erc20::ERC20Upgradeable"] +build-external-contracts = ["openzeppelin_presets::erc20::ERC20Upgradeable"] diff --git a/listings/applications/crowdfunding/src/campaign.cairo b/listings/applications/crowdfunding/src/campaign.cairo index c8892de0..e4f0b375 100644 --- a/listings/applications/crowdfunding/src/campaign.cairo +++ b/listings/applications/crowdfunding/src/campaign.cairo @@ -32,12 +32,13 @@ pub trait ICampaign { #[starknet::contract] pub mod Campaign { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components::ownable::ownable_component::OwnableInternalTrait; use core::num::traits::Zero; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ - ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, contract_address_const, - get_caller_address, get_contract_address, class_hash::class_hash_const + ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, get_caller_address, + get_contract_address }; use components::ownable::ownable_component; use super::pledgeable::pledgeable_component; @@ -154,7 +155,7 @@ pub mod Campaign { pub const ZERO_PLEDGES: felt252 = 'No pledges to claim'; } - const NINETY_DAYS: u64 = consteval_int!(90 * 24 * 60 * 60); + const NINETY_DAYS: u64 = 90 * 24 * 60 * 60; #[constructor] fn constructor( diff --git a/listings/applications/crowdfunding/src/campaign/pledgeable.cairo b/listings/applications/crowdfunding/src/campaign/pledgeable.cairo index 4000057a..c90290d8 100644 --- a/listings/applications/crowdfunding/src/campaign/pledgeable.cairo +++ b/listings/applications/crowdfunding/src/campaign/pledgeable.cairo @@ -14,13 +14,17 @@ pub trait IPledgeable { #[starknet::component] pub mod pledgeable_component { use core::array::ArrayTrait; - use starknet::{ContractAddress}; use core::num::traits::Zero; + use starknet::ContractAddress; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; #[storage] - struct Storage { - index_to_pledger: LegacyMap, - pledger_to_amount: LegacyMap, + pub struct Storage { + index_to_pledger: Map, + pledger_to_amount: Map, pledger_count: u32, total_amount: u256, } @@ -137,8 +141,8 @@ mod tests { impl Pledgeable = pledgeable_component::Pledgeable; } - use super::{pledgeable_component, IPledgeableDispatcher, IPledgeableDispatcherTrait}; - use super::pledgeable_component::{PledgeableImpl}; + use super::pledgeable_component; + use super::pledgeable_component::PledgeableImpl; use starknet::{ContractAddress, contract_address_const}; use core::num::traits::Zero; @@ -209,10 +213,9 @@ mod tests { assert_eq!(pledgeable.get_pledger_count(), expected_pledger_count); assert_eq!(pledgeable.get_total(), expected_total); - while let Option::Some((pledger, expected_amount)) = pledgers - .pop_front() { - assert_eq!(pledgeable.get(pledger), expected_amount); - } + while let Option::Some((pledger, expected_amount)) = pledgers.pop_front() { + assert_eq!(pledgeable.get(pledger), expected_amount); + } } #[test] @@ -466,7 +469,7 @@ mod tests { i -= 1; }; - // add last pledger + // add last pledger pledgeable.add(last_pledger, last_amount); expected_total += last_amount; @@ -528,13 +531,12 @@ mod tests { assert_eq!(pledgers_arr.len(), pledgers.len()); let mut i = 1000; - while let Option::Some(expected_pledger) = pledgers - .pop_front() { - i -= 1; - // pledgers are fetched in reversed order - let actual_pledger: ContractAddress = *pledgers_arr.at(i); - assert_eq!(expected_pledger, actual_pledger); - } + while let Option::Some(expected_pledger) = pledgers.pop_front() { + i -= 1; + // pledgers are fetched in reversed order + let actual_pledger: ContractAddress = *pledgers_arr.at(i); + assert_eq!(expected_pledger, actual_pledger); + } } #[test] diff --git a/listings/applications/crowdfunding/src/mock_upgrade.cairo b/listings/applications/crowdfunding/src/mock_upgrade.cairo index 27e9f1b5..de4c5e60 100644 --- a/listings/applications/crowdfunding/src/mock_upgrade.cairo +++ b/listings/applications/crowdfunding/src/mock_upgrade.cairo @@ -1,11 +1,12 @@ #[starknet::contract] pub mod MockUpgrade { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use components::ownable::ownable_component::OwnableInternalTrait; use core::num::traits::Zero; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ - ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, contract_address_const, - get_caller_address, get_contract_address, class_hash::class_hash_const + ClassHash, ContractAddress, SyscallResultTrait, get_block_timestamp, get_caller_address, + get_contract_address }; use components::ownable::ownable_component; use crowdfunding::campaign::pledgeable::pledgeable_component; @@ -95,7 +96,7 @@ pub mod MockUpgrade { pub implementation: ClassHash } - const NINETY_DAYS: u64 = consteval_int!(90 * 24 * 60 * 60); + const NINETY_DAYS: u64 = 90 * 24 * 60 * 60; #[constructor] fn constructor( diff --git a/listings/applications/crowdfunding/src/tests.cairo b/listings/applications/crowdfunding/src/tests.cairo index 6aff05d8..ba01cf83 100644 --- a/listings/applications/crowdfunding/src/tests.cairo +++ b/listings/applications/crowdfunding/src/tests.cairo @@ -1,12 +1,9 @@ -use core::clone::Clone; use core::result::ResultTrait; -use starknet::{ - ContractAddress, ClassHash, get_block_timestamp, contract_address_const, get_caller_address, -}; +use starknet::{ContractAddress, get_block_timestamp, contract_address_const,}; use snforge_std::{ declare, ContractClass, ContractClassTrait, start_cheat_caller_address, - stop_cheat_caller_address, spy_events, SpyOn, EventSpy, EventAssertions, get_class_hash, - cheat_block_timestamp_global + stop_cheat_caller_address, spy_events, EventSpyAssertionsTrait, get_class_hash, + DeclareResultTrait, start_cheat_block_timestamp_global }; use crowdfunding::campaign::{Campaign, ICampaignDispatcher, ICampaignDispatcherTrait}; @@ -92,7 +89,7 @@ fn deploy_with_token( fn test_deploy() { let start_time = get_block_timestamp(); let end_time = start_time + 60; - let contract = declare("Campaign").unwrap(); + let contract = *declare("Campaign").unwrap().contract_class(); let campaign = deploy( contract, "title 1", @@ -122,8 +119,8 @@ fn test_deploy() { #[test] fn test_successful_campaign() { - let token_class = declare("ERC20Upgradeable").unwrap(); - let contract_class = declare("Campaign").unwrap(); + let token_class = *declare("ERC20Upgradeable").unwrap().contract_class(); + let contract_class = *declare("Campaign").unwrap().contract_class(); let (campaign, token) = deploy_with_token(contract_class, token_class); let creator = contract_address_const::<'creator'>(); @@ -131,7 +128,7 @@ fn test_successful_campaign() { let pledger_2 = contract_address_const::<'pledger_2'>(); let pledger_3 = contract_address_const::<'pledger_3'>(); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); // 1st donation start_cheat_caller_address(campaign.contract_address, pledger_1); @@ -193,8 +190,8 @@ fn test_successful_campaign() { ] ); - // claim - cheat_block_timestamp_global(campaign.get_details().end_time); + // claim + start_cheat_block_timestamp_global(campaign.get_details().end_time); start_cheat_caller_address(campaign.contract_address, creator); prev_balance = token.balance_of(creator); campaign.claim(); @@ -214,14 +211,14 @@ fn test_successful_campaign() { #[test] fn test_upgrade_class_hash() { - let new_class_hash = declare("MockUpgrade").unwrap().class_hash; + let new_class_hash = *(declare("MockUpgrade").unwrap().contract_class()).class_hash; let owner = contract_address_const::<'owner'>(); // test pending campaign - let contract_class = declare("Campaign").unwrap(); - let token_class = declare("ERC20Upgradeable").unwrap(); + let contract_class = *declare("Campaign").unwrap().contract_class(); + let token_class = *declare("ERC20Upgradeable").unwrap().contract_class(); let (campaign, _) = deploy_with_token(contract_class, token_class); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); start_cheat_caller_address(campaign.contract_address, owner); campaign.upgrade(new_class_hash, Option::None); @@ -241,7 +238,7 @@ fn test_upgrade_class_hash() { // test active campaign let (campaign, token) = deploy_with_token(contract_class, token_class); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); let duration: u64 = 60; let pledger_1 = contract_address_const::<'pledger_1'>(); let pledger_2 = contract_address_const::<'pledger_2'>(); @@ -286,12 +283,12 @@ fn test_upgrade_class_hash() { #[test] fn test_cancel() { - let contract_class = declare("Campaign").unwrap(); - let token_class = declare("ERC20Upgradeable").unwrap(); + let contract_class = *declare("Campaign").unwrap().contract_class(); + let token_class = *declare("ERC20Upgradeable").unwrap().contract_class(); // test canceled campaign let (campaign, token) = deploy_with_token(contract_class, token_class); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); let creator = contract_address_const::<'creator'>(); let pledger_1 = contract_address_const::<'pledger_1'>(); let pledger_2 = contract_address_const::<'pledger_2'>(); @@ -340,7 +337,7 @@ fn test_cancel() { // test failed campaign let (campaign, token) = deploy_with_token(contract_class, token_class); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); let creator = contract_address_const::<'creator'>(); let pledger_1 = contract_address_const::<'pledger_1'>(); let pledger_2 = contract_address_const::<'pledger_2'>(); @@ -363,7 +360,7 @@ fn test_cancel() { assert_eq!(token.balance_of(pledger_2), prev_balance_pledger_2 - pledge_2); assert_eq!(token.balance_of(pledger_3), prev_balance_pledger_3 - pledge_3); - cheat_block_timestamp_global(campaign.get_details().end_time); + start_cheat_block_timestamp_global(campaign.get_details().end_time); start_cheat_caller_address(campaign.contract_address, creator); campaign.cancel("testing"); @@ -394,9 +391,10 @@ fn test_cancel() { fn test_refund() { // setup let (campaign, token) = deploy_with_token( - declare("Campaign").unwrap(), declare("ERC20Upgradeable").unwrap() + *declare("Campaign").unwrap().contract_class(), + *declare("ERC20Upgradeable").unwrap().contract_class() ); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); let creator = contract_address_const::<'creator'>(); let pledger_1 = contract_address_const::<'pledger_1'>(); let pledger_2 = contract_address_const::<'pledger_2'>(); @@ -445,9 +443,10 @@ fn test_refund() { fn test_unpledge() { // setup let (campaign, token) = deploy_with_token( - declare("Campaign").unwrap(), declare("ERC20Upgradeable").unwrap() + *declare("Campaign").unwrap().contract_class(), + *declare("ERC20Upgradeable").unwrap().contract_class() ); - let mut spy = spy_events(SpyOn::One(campaign.contract_address)); + let mut spy = spy_events(); let pledger = contract_address_const::<'pledger_1'>(); let amount: u256 = 3000; let prev_balance = token.balance_of(pledger); diff --git a/listings/applications/erc20/Scarb.toml b/listings/applications/erc20/Scarb.toml index e54db3f0..81a90227 100644 --- a/listings/applications/erc20/Scarb.toml +++ b/listings/applications/erc20/Scarb.toml @@ -1,13 +1,16 @@ [package] name = "erc20" version.workspace = true -edition = '2023_11' +edition = "2024_07" [lib] [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index c2477789..69370439 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -33,6 +33,10 @@ pub mod erc20 { use starknet::get_caller_address; use starknet::contract_address_const; use starknet::ContractAddress; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; #[storage] struct Storage { @@ -40,8 +44,8 @@ pub mod erc20 { symbol: felt252, decimals: u8, total_supply: felt252, - balances: LegacyMap::, - allowances: LegacyMap::<(ContractAddress, ContractAddress), felt252>, + balances: Map::, + allowances: Map::<(ContractAddress, ContractAddress), felt252>, } #[event] @@ -217,12 +221,11 @@ mod tests { use super::{erc20, IERC20Dispatcher, IERC20DispatcherTrait, erc20::{Event, Transfer, Approval}}; use starknet::{ - ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, - contract_address_const + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, contract_address_const }; use core::num::traits::Zero; - use starknet::testing::{set_contract_address, set_account_contract_address}; + use starknet::testing::set_contract_address; const token_name: felt252 = 'myToken'; const decimals: u8 = 18; diff --git a/listings/applications/nft_dutch_auction/Scarb.toml b/listings/applications/nft_dutch_auction/Scarb.toml index a6af238d..fea2ffae 100644 --- a/listings/applications/nft_dutch_auction/Scarb.toml +++ b/listings/applications/nft_dutch_auction/Scarb.toml @@ -1,14 +1,15 @@ [package] name = "nft_dutch_auction" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] erc20 = { path = "../erc20" } starknet.workspace = true +snforge_std.workspace = true [dev-dependencies] -snforge_std.workspace = true +assert_macros.workspace = true [scripts] test.workspace = true diff --git a/listings/applications/nft_dutch_auction/src/erc721.cairo b/listings/applications/nft_dutch_auction/src/erc721.cairo index 51ec11b6..b18c6a05 100644 --- a/listings/applications/nft_dutch_auction/src/erc721.cairo +++ b/listings/applications/nft_dutch_auction/src/erc721.cairo @@ -25,6 +25,10 @@ mod ERC721 { // library imports //////////////////////////////// use starknet::{ContractAddress, get_caller_address}; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; use core::num::traits::Zero; //////////////////////////////// @@ -34,11 +38,11 @@ mod ERC721 { struct Storage { name: felt252, symbol: felt252, - owners: LegacyMap::, - balances: LegacyMap::, - token_approvals: LegacyMap::, - operator_approvals: LegacyMap::<(ContractAddress, ContractAddress), bool>, - token_uri: LegacyMap, + owners: Map::, + balances: Map::, + token_approvals: Map::, + operator_approvals: Map::<(ContractAddress, ContractAddress), bool>, + token_uri: Map, } #[event] @@ -162,7 +166,7 @@ mod ERC721 { } //////////////////////////////// - // set_approval_for_all function approves an operator to spend all tokens + // set_approval_for_all function approves an operator to spend all tokens //////////////////////////////// fn set_approval_for_all( ref self: ContractState, operator: ContractAddress, approved: bool diff --git a/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo b/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo index aa85dd80..f139c2b1 100644 --- a/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo +++ b/listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo @@ -53,7 +53,8 @@ pub trait INFTDutchAuction { #[starknet::contract] pub mod NFTDutchAuction { use super::{IERC20Dispatcher, IERC20DispatcherTrait, IERC721Dispatcher, IERC721DispatcherTrait}; - use starknet::{ContractAddress, get_caller_address, get_contract_address, get_block_timestamp}; + use starknet::{ContractAddress, get_caller_address, get_block_timestamp}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { @@ -140,7 +141,7 @@ pub mod NFTDutchAuction { mod tests { use starknet::ContractAddress; use snforge_std::{ - BlockId, declare, ContractClassTrait, ContractClass, cheat_caller_address, CheatSpan, + declare, DeclareResultTrait, ContractClassTrait, cheat_caller_address, CheatSpan, cheat_block_timestamp }; use nft_dutch_auction::erc721::{IERC721Dispatcher, IERC721DispatcherTrait}; @@ -166,10 +167,10 @@ mod tests { pub const total_supply: felt252 = 2; fn get_contract_addresses() -> (ContractAddress, ContractAddress, ContractAddress) { - let erc721 = declare("ERC721").unwrap(); + let erc721 = declare("ERC721").unwrap().contract_class(); let erc721_constructor_calldata = array![erc721_name, erc721_symbol]; let (erc721_address, _) = erc721.deploy(@erc721_constructor_calldata).unwrap(); - let erc20 = declare("erc20").unwrap(); + let erc20 = declare("erc20").unwrap().contract_class(); let erc20_constructor_calldata = array![ erc20_recipient, erc20_name, @@ -178,7 +179,7 @@ mod tests { erc20_symbol ]; let (erc20_address, _) = erc20.deploy(@erc20_constructor_calldata).unwrap(); - let nft_auction = declare("NFTDutchAuction").unwrap(); + let nft_auction = declare("NFTDutchAuction").unwrap().contract_class(); let nft_auction_constructor_calldata = array![ erc20_address.into(), erc721_address.into(), @@ -234,7 +235,7 @@ mod tests { assert_eq!(buyer_bal_after_buy, buyer_bal_before_buy - nft_price); assert_eq!(erc721_dispatcher.owner_of(nft_id_1), buyer); - // Forward block timestamp in order for a reduced nft price + // Forward block timestamp in order for a reduced nft price let forward_blocktime_by = 4000; // milliseconds cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); @@ -276,7 +277,7 @@ mod tests { erc20_dispatcher.approve(nft_auction_address, nft_price); nft_auction_dispatcher.buy(nft_id_1); - // Forward block timestamp in order for a reduced nft price + // Forward block timestamp in order for a reduced nft price let forward_blocktime_by = 4000; // 4 seconds (in milliseconds) cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); @@ -321,7 +322,7 @@ mod tests { nft_auction_dispatcher.buy(nft_id_1); // Forward block timestamp to a time after duration has ended - // During deployment, duration was set to 60 seconds + // During deployment, duration was set to 60 seconds let forward_blocktime_by = 61000; // 61 seconds (in milliseconds) cheat_block_timestamp(nft_auction_address, forward_blocktime_by, CheatSpan::TargetCalls(1)); diff --git a/listings/applications/simple_storage_starknetjs/Scarb.toml b/listings/applications/simple_storage_starknetjs/Scarb.toml index f22b7903..56c2d211 100644 --- a/listings/applications/simple_storage_starknetjs/Scarb.toml +++ b/listings/applications/simple_storage_starknetjs/Scarb.toml @@ -1,13 +1,16 @@ [package] name = "simple_storage" version.workspace = true -edition = "2023_11" +edition = "2024_07" [lib] [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/simple_storage_starknetjs/src/storage.cairo b/listings/applications/simple_storage_starknetjs/src/storage.cairo index 38349241..b9f8805a 100644 --- a/listings/applications/simple_storage_starknetjs/src/storage.cairo +++ b/listings/applications/simple_storage_starknetjs/src/storage.cairo @@ -7,6 +7,8 @@ trait ISimpleStorage { #[starknet::contract] mod SimpleStorage { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { stored_data: u128 diff --git a/listings/applications/simple_vault/Scarb.toml b/listings/applications/simple_vault/Scarb.toml index 561214d3..11d2642c 100644 --- a/listings/applications/simple_vault/Scarb.toml +++ b/listings/applications/simple_vault/Scarb.toml @@ -1,12 +1,15 @@ [package] name = "simple_vault" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true erc20 = { path = "../erc20" } +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/simple_vault/src/lib.cairo b/listings/applications/simple_vault/src/lib.cairo index b5492840..b08098a0 100644 --- a/listings/applications/simple_vault/src/lib.cairo +++ b/listings/applications/simple_vault/src/lib.cairo @@ -1,5 +1,4 @@ mod simple_vault; - #[cfg(test)] mod tests; diff --git a/listings/applications/simple_vault/src/simple_vault.cairo b/listings/applications/simple_vault/src/simple_vault.cairo index 48b89d97..7251dce8 100644 --- a/listings/applications/simple_vault/src/simple_vault.cairo +++ b/listings/applications/simple_vault/src/simple_vault.cairo @@ -39,12 +39,16 @@ pub trait ISimpleVault { pub mod SimpleVault { use super::{IERC20Dispatcher, IERC20DispatcherTrait}; use starknet::{ContractAddress, get_caller_address, get_contract_address}; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; #[storage] struct Storage { token: IERC20Dispatcher, total_supply: u256, - balance_of: LegacyMap + balance_of: Map } #[constructor] @@ -81,7 +85,7 @@ pub mod SimpleVault { // T = total supply // s = shares to mint // - // (T + s) / T = (a + B) / B + // (T + s) / T = (a + B) / B // // s = aT / B let caller = get_caller_address(); @@ -107,7 +111,7 @@ pub mod SimpleVault { // T = total supply // s = shares to burn // - // (T - s) / T = (B - a) / B + // (T - s) / T = (B - a) / B // // a = sB / T let caller = get_caller_address(); @@ -125,19 +129,14 @@ pub mod SimpleVault { #[cfg(test)] mod tests { - use super::{ - SimpleVault, ISimpleVaultDispatcher, ISimpleVaultDispatcherTrait, IERC20Dispatcher, - IERC20DispatcherTrait - }; + use super::{SimpleVault, ISimpleVaultDispatcher, ISimpleVaultDispatcherTrait,}; use erc20::token::{ IERC20DispatcherTrait as IERC20DispatcherTrait_token, IERC20Dispatcher as IERC20Dispatcher_token }; - use core::num::traits::Zero; use starknet::testing::{set_contract_address, set_account_contract_address}; use starknet::{ - ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, get_caller_address, - contract_address_const + ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, contract_address_const }; const token_name: felt252 = 'myToken'; diff --git a/listings/applications/staking/Scarb.toml b/listings/applications/staking/Scarb.toml index 7ac644c6..93510593 100644 --- a/listings/applications/staking/Scarb.toml +++ b/listings/applications/staking/Scarb.toml @@ -1,12 +1,15 @@ [package] name = "staking" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true openzeppelin.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/staking/src/contract.cairo b/listings/applications/staking/src/contract.cairo index 21134ca0..5980b2c0 100644 --- a/listings/applications/staking/src/contract.cairo +++ b/listings/applications/staking/src/contract.cairo @@ -26,25 +26,29 @@ pub mod StakingContract { use core::num::traits::Zero; use starknet::{ContractAddress, get_caller_address, get_block_timestamp, get_contract_address}; use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + use starknet::storage::{ + Map, StorageMapReadAccess, StorageMapWriteAccess, StoragePointerReadAccess, + StoragePointerWriteAccess + }; #[storage] struct Storage { - staking_token: IERC20Dispatcher, - reward_token: IERC20Dispatcher, - owner: ContractAddress, - reward_rate: u256, - duration: u256, - current_reward_per_staked_token: u256, - finish_at: u256, + pub staking_token: IERC20Dispatcher, + pub reward_token: IERC20Dispatcher, + pub owner: ContractAddress, + pub reward_rate: u256, + pub duration: u256, + pub current_reward_per_staked_token: u256, + pub finish_at: u256, // last time an operation (staking / withdrawal / rewards claimed) was registered - last_updated_at: u256, - last_user_reward_per_staked_token: LegacyMap::, - unclaimed_rewards: LegacyMap::, - total_distributed_rewards: u256, + pub last_updated_at: u256, + pub last_user_reward_per_staked_token: Map::, + pub unclaimed_rewards: Map::, + pub total_distributed_rewards: u256, // total amount of staked tokens - total_supply: u256, + pub total_supply: u256, // amount of staked tokens per user - balance_of: LegacyMap::, + pub balance_of: Map::, } #[event] @@ -125,7 +129,8 @@ pub mod StakingContract { self.reward_rate.write(rate); - // even if the previous reward duration has not finished, we reset the finish_at variable + // even if the previous reward duration has not finished, we reset the finish_at + // variable self.finish_at.write(block_timestamp + self.duration.read()); self.last_updated_at.write(block_timestamp); @@ -223,7 +228,8 @@ pub mod StakingContract { // owner should set up NEW rewards into the contract self.emit(RewardsFinished { msg: 'Rewards all distributed' }); } else { - // owner should set up rewards into the contract (or add duration by setting up rewards) + // owner should set up rewards into the contract (or add duration by setting up + // rewards) self.emit(RewardsFinished { msg: 'Rewards not active yet' }); } } @@ -248,7 +254,7 @@ pub mod StakingContract { #[inline(always)] fn last_time_applicable(self: @ContractState) -> u256 { - PrivateFunctions::min(self.finish_at.read(), get_block_timestamp().into()) + Self::min(self.finish_at.read(), get_block_timestamp().into()) } #[inline(always)] diff --git a/listings/applications/staking/src/tests/staking_tests.cairo b/listings/applications/staking/src/tests/staking_tests.cairo index 4e53031b..d899b876 100644 --- a/listings/applications/staking/src/tests/staking_tests.cairo +++ b/listings/applications/staking/src/tests/staking_tests.cairo @@ -1,27 +1,19 @@ mod tests { - use staking::contract::StakingContract::__member_module_unclaimed_rewards::InternalContractMemberStateTrait as UncRewInt; - use staking::contract::StakingContract::__member_module_last_updated_at::InternalContractMemberStateTrait as LastUpInt; - use staking::contract::StakingContract::__member_module_finish_at::InternalContractMemberStateTrait as FinInt; - use staking::contract::StakingContract::__member_module_reward_rate::InternalContractMemberStateTrait as RewRateInt; - use staking::contract::StakingContract::__member_module_total_supply::InternalContractMemberStateTrait as TotSupInt; - use staking::contract::StakingContract::__member_module_balance_of::InternalContractMemberStateTrait as BalInt; - use staking::contract::StakingContract::__member_module_staking_token::InternalContractMemberStateTrait as StakeTokInt; - use staking::contract::StakingContract::__member_module_reward_token::InternalContractMemberStateTrait as RewardTokInt; use openzeppelin::token::erc20::interface::IERC20DispatcherTrait; use openzeppelin::token::erc20::erc20::ERC20Component::InternalTrait; use staking::contract::IStakingContractDispatcherTrait; use staking::tests::tokens::{RewardToken, StakingToken}; use staking::contract::{ - StakingContract, IStakingContractDispatcher, StakingContract::ownerContractMemberStateTrait, - StakingContract::Event, StakingContract::Deposit, StakingContract::Withdrawal, - StakingContract::RewardsFinished + StakingContract, IStakingContractDispatcher, StakingContract::Event, + StakingContract::Deposit, StakingContract::Withdrawal, StakingContract::RewardsFinished }; use openzeppelin::token::erc20::{interface::IERC20Dispatcher}; use starknet::syscalls::deploy_syscall; use starknet::SyscallResultTrait; use core::serde::Serde; - use starknet::testing::{set_caller_address, set_contract_address, set_block_timestamp, pop_log}; - use starknet::{contract_address_const, ContractAddress, get_contract_address}; + use starknet::testing::{set_contract_address, set_block_timestamp, pop_log}; + use starknet::{contract_address_const, ContractAddress}; + use starknet::storage::{StoragePointerReadAccess, StorageMapReadAccess}; #[derive(Copy, Drop)] struct Deployment { @@ -107,7 +99,6 @@ mod tests { } #[test] - #[available_gas(20000000)] fn should_deploy() { /// setup let owner = contract_address_const::<'owner'>(); @@ -118,24 +109,21 @@ mod tests { // "link" a new StakingContract struct to the deployed StakingContract contract // in order to access its internal state fields for assertions - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); // pretend as if we were in the deployed contract set_contract_address(deploy.contract.contract_address); /// then - assert(state.owner.read() == owner, 'wrong owner'); - assert( - state.staking_token.read().contract_address == deploy.staking_token.contract_address, - 'wrong staking token contract' + assert_eq!(state.owner.read(), owner); + assert_eq!( + state.staking_token.read().contract_address, deploy.staking_token.contract_address ); - assert( - state.reward_token.read().contract_address == deploy.reward_token.contract_address, - 'wrong reward token contract' + assert_eq!( + state.reward_token.read().contract_address, deploy.reward_token.contract_address ); } #[test] - #[available_gas(20000000)] fn stake_and_withdraw_succeed() { /// set up @@ -156,14 +144,11 @@ mod tests { /// then // so far the user has 60 tokens left and staked 40 tokens - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); set_contract_address(deploy.contract.contract_address); - assert(state.balance_of.read(user) == stake_amount, '1- wrong user staking balance'); - assert(state.total_supply.read() == stake_amount, '1- wrong total supply'); - assert( - deploy.staking_token.balance_of(user) == amount_tokens_minted - stake_amount, - '1- wrong staking token balance' - ); + assert_eq!(state.balance_of.read(user), stake_amount); + assert_eq!(state.total_supply.read(), stake_amount); + assert_eq!(deploy.staking_token.balance_of(user), amount_tokens_minted - stake_amount); // check 1st & 2nd event - when user stakes assert_eq!( @@ -182,20 +167,13 @@ mod tests { /// then // at the end the user has 80 tokens left and 20 tokens staked - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); set_contract_address(deploy.contract.contract_address); - assert( - state.balance_of.read(user) == stake_amount - withdrawal_amount, - '2- wrong user staking balance' - ); - assert( - state.total_supply.read() == stake_amount - withdrawal_amount, '2- wrong total supply' - ); - assert( - deploy.staking_token.balance_of(user) == amount_tokens_minted - - stake_amount - + withdrawal_amount, - '2- wrong staking token balance' + assert_eq!(state.balance_of.read(user), stake_amount - withdrawal_amount); + assert_eq!(state.total_supply.read(), stake_amount - withdrawal_amount); + assert_eq!( + deploy.staking_token.balance_of(user), + amount_tokens_minted - stake_amount + withdrawal_amount ); // check 3rd & 4th events - when user withdraws @@ -210,7 +188,6 @@ mod tests { } #[test] - #[available_gas(20000000)] fn claim_rewards_3_users_scenario() { /// set up @@ -231,21 +208,18 @@ mod tests { let block_timestamp: u256 = 1000; set_block_timestamp(block_timestamp.try_into().unwrap()); let reward_duration = 100; - // have to set again the contract_address because it got changed in mint_reward_tokens_to function + // have to set again the contract_address because it got changed in mint_reward_tokens_to + // function set_contract_address(owner); deploy.contract.set_reward_duration(reward_duration); deploy.contract.set_reward_amount(reward_tokens_amount); // check reward rate, last updated at and finish dates - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); set_contract_address(deploy.contract.contract_address); - assert( - state.reward_rate.read() == reward_tokens_amount / reward_duration, 'Wrong reward rate' - ); - assert( - state.finish_at.read() == block_timestamp + reward_duration, 'Wrong reward finish date' - ); - assert(state.last_updated_at.read() == block_timestamp, 'Wrong reward last updated date'); + assert_eq!(state.reward_rate.read(), reward_tokens_amount / reward_duration); + assert_eq!(state.finish_at.read(), block_timestamp + reward_duration); + assert_eq!(state.last_updated_at.read(), block_timestamp); // mint staking tokens to alice let alice = contract_address_const::<'alice'>(); @@ -370,9 +344,11 @@ mod tests { // timestamp = 1110 // r6 = r5 + 10 * (1100 - 1090) / 10 = 15 + 10 = 25 // last updated at = 1100 (becomes same as finish_at) - // alice rewards = 600 + staked_tokens * (r6 - r5) = 600 + 0 * (25 - 15) = 600 -> 0 (claimed) + // alice rewards = 600 + staked_tokens * (r6 - r5) = 600 + 0 * (25 - 15) = 600 -> 0 + // (claimed) // bob rewards = 40 + staked_tokens * (r6 - r3) = 40 + 0 * (25 - 9) = 40 -> 0 (claimed) - // john rewards = 180 + staked_tokens * (r6 - r4) = 180 + 10 * (25 - 13) = 300 -> 0 (claimed) + // john rewards = 180 + staked_tokens * (r6 - r4) = 180 + 10 * (25 - 13) = 300 -> 0 + // (claimed) // last RPST for alice = r6 = 25 // last RPST for bob = r6 = 25 // last RPST for john = r6 = 25 @@ -380,7 +356,7 @@ mod tests { /// then - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); set_contract_address(deploy.contract.contract_address); // check amount of unclaimed reward tokens for each user @@ -400,27 +376,22 @@ mod tests { let deployed_contract_rewards = deploy .reward_token .balance_of(deploy.contract.contract_address); - assert(alice_rewards == 600, 'Alice: wrong amount of rewards'); - assert(bob_rewards == 40, 'Bob: wrong amount of rewards'); - assert(john_rewards == 300, 'John: wrong amount of rewards'); + assert_eq!(alice_rewards, 600); + assert_eq!(bob_rewards, 40); + assert_eq!(john_rewards, 300); // 1000 - 600 - 40 - 300 = 60 - assert(deployed_contract_rewards == 60, 'Contract: wrong rewards'); + assert_eq!(deployed_contract_rewards, 60); // check amount of staking tokens each user has back in their balance let alice_staking_tokens = deploy.staking_token.balance_of(alice); let bob_staking_tokens = deploy.staking_token.balance_of(bob); let john_staking_tokens = deploy.staking_token.balance_of(john); - assert( - alice_staking_tokens == alice_amount_tokens_minted, 'Alice: wrong amount of staking' - ); - assert(bob_staking_tokens == bob_amount_tokens_minted, 'Bob: wrong amount of staking'); - assert( - john_staking_tokens == john_amount_tokens_minted - 10, 'John: wrong amount of staking' - ); + assert_eq!(alice_staking_tokens, alice_amount_tokens_minted); + assert_eq!(bob_staking_tokens, bob_amount_tokens_minted); + assert_eq!(john_staking_tokens, john_amount_tokens_minted - 10); } #[test] - #[available_gas(20000000)] fn all_rewards_distributed_event() { /// set up @@ -441,7 +412,8 @@ mod tests { let block_timestamp: u256 = 1000; set_block_timestamp(block_timestamp.try_into().unwrap()); let reward_duration = 100; - // have to set again the contract_address because it got changed in mint_reward_tokens_to function + // have to set again the contract_address because it got changed in mint_reward_tokens_to + // function set_contract_address(owner); deploy.contract.set_reward_duration(reward_duration); deploy.contract.set_reward_amount(reward_tokens_amount); @@ -496,7 +468,6 @@ mod tests { } #[test] - #[available_gas(20000000)] fn set_up_reward_complex() { /// Set up @@ -518,22 +489,23 @@ mod tests { set_block_timestamp(block_timestamp.try_into().unwrap()); let reward_duration = 100; let initial_reward = 400; - // have to set again the contract_address because it got changed in mint_reward_tokens_to function + // have to set again the contract_address because it got changed in mint_reward_tokens_to + // function set_contract_address(owner); deploy.contract.set_reward_duration(reward_duration); deploy.contract.set_reward_amount(initial_reward); // middle check - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); set_contract_address(deploy.contract.contract_address); // timestamp = 1000 // finish_at = 1100 // reward_rate = 400 / 100 = 4 tokens/timestamp_unit - assert(state.finish_at.read() == block_timestamp + reward_duration, '1- Wrong finish date'); - assert(state.last_updated_at.read() == block_timestamp, '1- Wrong last update date'); - assert(state.reward_rate.read() == 4, '1- Wrong reward rate'); + assert_eq!(state.finish_at.read(), block_timestamp + reward_duration); + assert_eq!(state.last_updated_at.read(), block_timestamp); + assert_eq!(state.reward_rate.read(), 4); /// When @@ -553,14 +525,12 @@ mod tests { // new_reward_rate = (200 + 300) / 100 = 5 // new_finish_at = 1050 + 100 = 1150 - let state = StakingContract::contract_state_for_testing(); + let state = @StakingContract::contract_state_for_testing(); set_contract_address(deploy.contract.contract_address); // the finish_at date is reset - assert( - state.finish_at.read() == middle_timestamp + reward_duration, '2- Wrong finish date' - ); - assert(state.last_updated_at.read() == middle_timestamp, '2- Wrong last update date'); - assert(state.reward_rate.read() == 5, ''); + assert_eq!(state.finish_at.read(), middle_timestamp + reward_duration); + assert_eq!(state.last_updated_at.read(), middle_timestamp); + assert_eq!(state.reward_rate.read(), 5); } } diff --git a/listings/applications/staking/src/tests/tokens.cairo b/listings/applications/staking/src/tests/tokens.cairo index ea788565..0b0148c6 100644 --- a/listings/applications/staking/src/tests/tokens.cairo +++ b/listings/applications/staking/src/tests/tokens.cairo @@ -12,7 +12,7 @@ pub mod RewardToken { #[storage] struct Storage { #[substorage(v0)] - erc20: ERC20Component::Storage, + pub erc20: ERC20Component::Storage, } #[event] @@ -49,7 +49,7 @@ pub mod StakingToken { #[storage] struct Storage { #[substorage(v0)] - erc20: ERC20Component::Storage, + pub erc20: ERC20Component::Storage, } #[event] diff --git a/listings/applications/timelock/Scarb.toml b/listings/applications/timelock/Scarb.toml index a3eaafbf..eed924b6 100644 --- a/listings/applications/timelock/Scarb.toml +++ b/listings/applications/timelock/Scarb.toml @@ -1,17 +1,17 @@ [package] name = "timelock" version.workspace = true -edition = "2023_11" +edition = "2024_07" [dependencies] starknet.workspace = true -# Starknet Foundry: snforge_std.workspace = true -# OpenZeppelin: openzeppelin.workspace = true -# StarknetByExample Components components.workspace = true +[dev-dependencies] +assert_macros.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/timelock/src/tests/timelock.cairo b/listings/applications/timelock/src/tests/timelock.cairo index f3e6c871..dd0c98da 100644 --- a/listings/applications/timelock/src/tests/timelock.cairo +++ b/listings/applications/timelock/src/tests/timelock.cairo @@ -1,11 +1,9 @@ use core::panic_with_felt252; -use starknet::get_block_timestamp; use starknet::account::Call; use core::poseidon::{PoseidonTrait, poseidon_hash_span}; -use core::hash::{HashStateTrait, HashStateExTrait}; +use core::hash::HashStateTrait; use snforge_std::{ - cheat_caller_address, cheat_block_timestamp, CheatSpan, spy_events, SpyOn, EventSpy, - EventAssertions + cheat_caller_address, cheat_block_timestamp, CheatSpan, spy_events, EventSpyAssertionsTrait }; use openzeppelin::token::erc721::interface::IERC721DispatcherTrait; use openzeppelin::token::erc721::erc721::ERC721Component; @@ -75,7 +73,7 @@ fn test_queue_timestamp_not_in_range() { #[test] fn test_queue_success() { let timelock_test = TimeLockTestTrait::setup(); - let mut spy = spy_events(SpyOn::One(timelock_test.timelock_address)); + let mut spy = spy_events(); let timestamp = timelock_test.get_timestamp(); let tx_id = timelock_test.timelock.queue(timelock_test.get_call(), timestamp); spy @@ -154,7 +152,7 @@ fn test_execute_success() { timelock_test.timelock.queue(timelock_test.get_call(), timestamp); timelock_test.erc721.approve(timelock_test.timelock_address, TOKEN_ID); cheat_block_timestamp(timelock_test.timelock_address, timestamp + 1, CheatSpan::TargetCalls(1)); - let mut spy = spy_events(SpyOn::One(timelock_test.timelock_address)); + let mut spy = spy_events(); timelock_test.timelock.execute(timelock_test.get_call(), timestamp); spy .assert_emitted( @@ -214,7 +212,7 @@ fn test_cancel_success() { let tx_id = timelock_test .timelock .queue(timelock_test.get_call(), timelock_test.get_timestamp()); - let mut spy = spy_events(SpyOn::One(timelock_test.timelock_address)); + let mut spy = spy_events(); timelock_test.timelock.cancel(tx_id); spy .assert_emitted( diff --git a/listings/applications/timelock/src/tests/utils.cairo b/listings/applications/timelock/src/tests/utils.cairo index c8de72a5..32973908 100644 --- a/listings/applications/timelock/src/tests/utils.cairo +++ b/listings/applications/timelock/src/tests/utils.cairo @@ -1,6 +1,6 @@ use starknet::{ContractAddress, contract_address_const, get_block_timestamp}; use starknet::account::Call; -use snforge_std::{declare, ContractClassTrait, test_address}; +use snforge_std::{declare, ContractClassTrait, test_address, DeclareResultTrait}; use openzeppelin::utils::serde::SerializedAppend; use openzeppelin::token::erc721::interface::IERC721Dispatcher; use timelock::timelock::{TimeLock, ITimeLockDispatcher, ITimeLockSafeDispatcher}; @@ -35,12 +35,12 @@ pub struct TimeLockTest { #[generate_trait] pub impl TimeLockTestImpl of TimeLockTestTrait { fn setup() -> TimeLockTest { - let timelock_contract = declare("TimeLock").unwrap(); + let timelock_contract = *declare("TimeLock").unwrap().contract_class(); let mut timelock_calldata = array![]; let (timelock_address, _) = timelock_contract.deploy(@timelock_calldata).unwrap(); let timelock = ITimeLockDispatcher { contract_address: timelock_address }; let timelock_safe = ITimeLockSafeDispatcher { contract_address: timelock_address }; - let erc721_contract = declare("ERC721").unwrap(); + let erc721_contract = *declare("ERC721").unwrap().contract_class(); let mut erc721_calldata = array![]; erc721_calldata.append_serde(NAME()); erc721_calldata.append_serde(SYMBOL()); diff --git a/listings/applications/timelock/src/timelock.cairo b/listings/applications/timelock/src/timelock.cairo index 29081790..38e42ed9 100644 --- a/listings/applications/timelock/src/timelock.cairo +++ b/listings/applications/timelock/src/timelock.cairo @@ -1,4 +1,3 @@ -use starknet::ContractAddress; use starknet::account::Call; #[starknet::interface] @@ -12,12 +11,11 @@ pub trait ITimeLock { #[starknet::contract] pub mod TimeLock { use core::poseidon::{PoseidonTrait, poseidon_hash_span}; - use core::hash::{HashStateTrait, HashStateExTrait}; - use starknet::{ - ContractAddress, get_caller_address, get_block_timestamp, SyscallResultTrait, syscalls - }; + use core::hash::HashStateTrait; + use starknet::{get_caller_address, get_block_timestamp, SyscallResultTrait, syscalls}; use starknet::account::Call; use components::ownable::ownable_component; + use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; component!(path: ownable_component, storage: ownable, event: OwnableEvent); @@ -30,7 +28,7 @@ pub mod TimeLock { struct Storage { #[substorage(v0)] ownable: ownable_component::Storage, - queued: LegacyMap::, + queued: Map::, } #[event] diff --git a/listings/applications/upgradeable_contract/Scarb.toml b/listings/applications/upgradeable_contract/Scarb.toml index 8256c5fa..c4dcec0d 100644 --- a/listings/applications/upgradeable_contract/Scarb.toml +++ b/listings/applications/upgradeable_contract/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "upgradeable_contract" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/applications/upgradeable_contract/src/tests.cairo b/listings/applications/upgradeable_contract/src/tests.cairo index 3c3e7ee2..a60886a6 100644 --- a/listings/applications/upgradeable_contract/src/tests.cairo +++ b/listings/applications/upgradeable_contract/src/tests.cairo @@ -34,7 +34,7 @@ mod tests { ) } - // deploy v1 contract + // deploy v1 contract fn deploy_v1() -> (IUpgradeableContractDispatcher_v1, ContractAddress, ClassHash) { let (contract_address, _) = deploy_syscall( UpgradeableContract_V1::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false diff --git a/listings/getting-started/bytearray/Scarb.toml b/listings/getting-started/bytearray/Scarb.toml index 5b76d327..21e61cdd 100644 --- a/listings/getting-started/bytearray/Scarb.toml +++ b/listings/getting-started/bytearray/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "bytearray" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/bytearray/src/bytearray.cairo b/listings/getting-started/bytearray/src/bytearray.cairo index 43793bd1..52d8de06 100644 --- a/listings/getting-started/bytearray/src/bytearray.cairo +++ b/listings/getting-started/bytearray/src/bytearray.cairo @@ -7,9 +7,11 @@ pub trait IMessage { // ANCHOR: contract #[starknet::contract] pub mod MessageContract { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { - message: ByteArray + pub message: ByteArray } #[constructor] @@ -32,9 +34,8 @@ pub mod MessageContract { #[cfg(test)] mod tests { - use bytearray::bytearray::{ - MessageContract::messageContractMemberStateTrait, MessageContract, IMessage - }; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + use bytearray::bytearray::{MessageContract, IMessage}; #[test] #[available_gas(2000000000)] diff --git a/listings/getting-started/cairo_cheatsheet/Scarb.toml b/listings/getting-started/cairo_cheatsheet/Scarb.toml index 138959f1..b1ec0393 100644 --- a/listings/getting-started/cairo_cheatsheet/Scarb.toml +++ b/listings/getting-started/cairo_cheatsheet/Scarb.toml @@ -1,12 +1,16 @@ [package] name = "cairo_cheatsheet" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true [[target.starknet-contract]] +allowed-libfuncs-list.name = "experimental" diff --git a/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo b/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo index 13a70676..6cbd748d 100644 --- a/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/dict_example.cairo @@ -1,4 +1,6 @@ // ANCHOR: sheet +use core::dict::Felt252Dict; + fn dict() { let mut Auctions: Felt252Dict = Default::default(); diff --git a/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo b/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo index 8364485c..8695bb38 100644 --- a/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/enum_example.cairo @@ -31,7 +31,7 @@ trait IEnumContract { #[starknet::contract] mod EnumContract { - use core::clone::Clone; + use starknet::storage::StoragePointerWriteAccess; use super::IEnumContract; use super::{Action, Position, UserCommand}; diff --git a/listings/getting-started/cairo_cheatsheet/src/felt_example.cairo b/listings/getting-started/cairo_cheatsheet/src/felt_example.cairo index d556fd8c..43ab4360 100644 --- a/listings/getting-started/cairo_cheatsheet/src/felt_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/felt_example.cairo @@ -4,5 +4,5 @@ fn felt() { let felt_as_str = 'Hello Starknet!'; let _felt = felt + felt_as_str; -// ANCHOR_END: sheet + // ANCHOR_END: sheet } diff --git a/listings/getting-started/cairo_cheatsheet/src/loop_example.cairo b/listings/getting-started/cairo_cheatsheet/src/loop_example.cairo index 5abd9885..65860332 100644 --- a/listings/getting-started/cairo_cheatsheet/src/loop_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/loop_example.cairo @@ -14,5 +14,5 @@ fn do_loop() { i += 1; }; -// ANCHOR_END: sheet + // ANCHOR_END: sheet } diff --git a/listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo b/listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo index bd107b91..176a2240 100644 --- a/listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo @@ -15,11 +15,12 @@ trait IMappingExample { #[starknet::contract] mod MappingContract { use starknet::ContractAddress; + use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; #[storage] struct Storage { - students_name: LegacyMap::, - students_result_record: LegacyMap::<(ContractAddress, felt252), u16>, + students_name: Map::, + students_result_record: Map::<(ContractAddress, felt252), u16>, } #[abi(embed_v0)] diff --git a/listings/getting-started/cairo_cheatsheet/src/tuple_example.cairo b/listings/getting-started/cairo_cheatsheet/src/tuple_example.cairo index dab039b0..4f54e255 100644 --- a/listings/getting-started/cairo_cheatsheet/src/tuple_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/tuple_example.cairo @@ -9,5 +9,5 @@ fn tuple() { // Access tuple let (address, age, active) = stored_tuple; -// ANCHOR_END: sheet + // ANCHOR_END: sheet } diff --git a/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo b/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo index 17150e32..9e2bbfea 100644 --- a/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo @@ -23,5 +23,5 @@ fn type_casting() { // Note: usize is smaller than felt252, so we use try_into let _new_usize: usize = my_felt252.try_into().unwrap(); -// ANCHOR_END: sheet + // ANCHOR_END: sheet } diff --git a/listings/getting-started/cairo_cheatsheet/src/while_example.cairo b/listings/getting-started/cairo_cheatsheet/src/while_example.cairo index c9011114..c681ac2c 100644 --- a/listings/getting-started/cairo_cheatsheet/src/while_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/while_example.cairo @@ -7,5 +7,5 @@ fn do_loop() { arr.append(i); i += 1; }; -// ANCHOR_END: sheet + // ANCHOR_END: sheet } diff --git a/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo b/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo index edc475e0..6e3035af 100644 --- a/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo +++ b/listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo @@ -4,15 +4,14 @@ fn while_let() { // "while `let` destructures `option` into `Some(i)`, // evaluate the block (`{}`), else `break` - while let Option::Some(i) = - option { - if i > 0 { - println!("Greater than 0, break..."); - option = Option::None; - } else { - println!("`i` is `{:?}`. Try again.", i); - option = Option::Some(i + 1); - } + while let Option::Some(i) = option { + if i > 0 { + println!("Greater than 0, break..."); + option = Option::None; + } else { + println!("`i` is `{:?}`. Try again.", i); + option = Option::Some(i + 1); } -// ANCHOR_END: sheet + } + // ANCHOR_END: sheet } diff --git a/listings/getting-started/calling_other_contracts/Scarb.toml b/listings/getting-started/calling_other_contracts/Scarb.toml index 39c622eb..f1727dbd 100644 --- a/listings/getting-started/calling_other_contracts/Scarb.toml +++ b/listings/getting-started/calling_other_contracts/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "calling_other_contracts" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/calling_other_contracts/src/caller.cairo b/listings/getting-started/calling_other_contracts/src/caller.cairo index 35ac8381..b82ff2e3 100644 --- a/listings/getting-started/calling_other_contracts/src/caller.cairo +++ b/listings/getting-started/calling_other_contracts/src/caller.cairo @@ -7,9 +7,11 @@ pub trait ICallee { #[starknet::contract] pub mod Callee { + use starknet::storage::StoragePointerWriteAccess; + #[storage] struct Storage { - value: u128, + pub value: u128, } #[abi(embed_v0)] @@ -51,14 +53,9 @@ pub mod Caller { #[cfg(test)] mod tests { - use super::{ - Callee, ICalleeDispatcher, ICalleeDispatcherTrait, Callee::valueContractMemberStateTrait, - Caller, ICallerDispatcher, ICallerDispatcherTrait - }; - use starknet::{ - ContractAddress, contract_address_const, testing::set_contract_address, - syscalls::deploy_syscall, SyscallResultTrait - }; + use super::{Callee, ICalleeDispatcher, Caller, ICallerDispatcher, ICallerDispatcherTrait}; + use starknet::{testing::set_contract_address, syscalls::deploy_syscall, SyscallResultTrait}; + use starknet::storage::StoragePointerReadAccess; fn deploy() -> (ICalleeDispatcher, ICallerDispatcher) { let (address_callee, _) = deploy_syscall( @@ -82,7 +79,7 @@ mod tests { let (callee, caller) = deploy(); caller.set_value_from_address(callee.contract_address, init_value); - let state = Callee::contract_state_for_testing(); + let state = @Callee::contract_state_for_testing(); set_contract_address(callee.contract_address); let value_read: u128 = state.value.read(); diff --git a/listings/getting-started/constructor/Scarb.toml b/listings/getting-started/constructor/Scarb.toml index 20437aeb..8235973e 100644 --- a/listings/getting-started/constructor/Scarb.toml +++ b/listings/getting-started/constructor/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "constructor" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/constructor/src/constructor.cairo b/listings/getting-started/constructor/src/constructor.cairo index be30f961..bf68ac8d 100644 --- a/listings/getting-started/constructor/src/constructor.cairo +++ b/listings/getting-started/constructor/src/constructor.cairo @@ -2,10 +2,11 @@ #[starknet::contract] pub mod ExampleConstructor { use starknet::ContractAddress; + use starknet::storage::{Map, StorageMapWriteAccess}; #[storage] struct Storage { - names: LegacyMap::, + pub names: Map::, } // The constructor is decorated with a `#[constructor]` attribute. @@ -19,9 +20,10 @@ pub mod ExampleConstructor { #[cfg(test)] mod tests { - use super::{ExampleConstructor, ExampleConstructor::namesContractMemberStateTrait}; + use super::ExampleConstructor; use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; use starknet::{contract_address_const, testing::{set_contract_address}}; + use starknet::storage::StorageMapReadAccess; #[test] fn should_deploy_with_constructor_init_value() { @@ -36,7 +38,7 @@ mod tests { ) .unwrap_syscall(); - let state = ExampleConstructor::contract_state_for_testing(); + let state = @ExampleConstructor::contract_state_for_testing(); set_contract_address(contract_address); let name = state.names.read(address); diff --git a/listings/getting-started/counter/Scarb.toml b/listings/getting-started/counter/Scarb.toml index 64469a3a..1a78d653 100644 --- a/listings/getting-started/counter/Scarb.toml +++ b/listings/getting-started/counter/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "counter" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/counter/src/counter.cairo b/listings/getting-started/counter/src/counter.cairo index 0a719b8d..83d8e287 100644 --- a/listings/getting-started/counter/src/counter.cairo +++ b/listings/getting-started/counter/src/counter.cairo @@ -8,10 +8,12 @@ pub trait ISimpleCounter { // ANCHOR: contract #[starknet::contract] pub mod SimpleCounter { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { // Counter variable - counter: u128, + pub counter: u128, } #[constructor] @@ -44,7 +46,7 @@ pub mod SimpleCounter { #[cfg(test)] mod test { use super::{SimpleCounter, ISimpleCounterDispatcher, ISimpleCounterDispatcherTrait}; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; fn deploy(init_value: u128) -> ISimpleCounterDispatcher { let (contract_address, _) = deploy_syscall( diff --git a/listings/getting-started/custom_type_serde/Scarb.toml b/listings/getting-started/custom_type_serde/Scarb.toml index 74569a87..5414b1dd 100644 --- a/listings/getting-started/custom_type_serde/Scarb.toml +++ b/listings/getting-started/custom_type_serde/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "custom_type_serde" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/custom_type_serde/src/contract.cairo b/listings/getting-started/custom_type_serde/src/contract.cairo index 9a320b75..b70142cc 100644 --- a/listings/getting-started/custom_type_serde/src/contract.cairo +++ b/listings/getting-started/custom_type_serde/src/contract.cairo @@ -36,7 +36,7 @@ mod tests { use super::{ SerdeCustomType, Person, ISerdeCustomTypeDispatcher, ISerdeCustomTypeDispatcherTrait }; - use starknet::{ContractAddress, syscalls::deploy_syscall, SyscallResultTrait}; + use starknet::{syscalls::deploy_syscall, SyscallResultTrait}; fn deploy() -> ISerdeCustomTypeDispatcher { let (contract_address, _) = deploy_syscall( diff --git a/listings/getting-started/errors/Scarb.toml b/listings/getting-started/errors/Scarb.toml index 4f180125..2b8cc106 100644 --- a/listings/getting-started/errors/Scarb.toml +++ b/listings/getting-started/errors/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "errors" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/errors/src/custom_errors.cairo b/listings/getting-started/errors/src/custom_errors.cairo index eca2e00c..d7216f96 100644 --- a/listings/getting-started/errors/src/custom_errors.cairo +++ b/listings/getting-started/errors/src/custom_errors.cairo @@ -37,7 +37,7 @@ mod test { use super::{ CustomErrorsExample, ICustomErrorsExampleDispatcher, ICustomErrorsExampleDispatcherTrait }; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; fn deploy() -> ICustomErrorsExampleDispatcher { let (contract_address, _) = deploy_syscall( diff --git a/listings/getting-started/errors/src/simple_errors.cairo b/listings/getting-started/errors/src/simple_errors.cairo index 28886b0a..d1d387cd 100644 --- a/listings/getting-started/errors/src/simple_errors.cairo +++ b/listings/getting-started/errors/src/simple_errors.cairo @@ -31,7 +31,7 @@ pub mod ErrorsExample { #[cfg(test)] mod test { use super::{ErrorsExample, IErrorsExampleDispatcher, IErrorsExampleDispatcherTrait}; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; fn deploy() -> IErrorsExampleDispatcher { let (contract_address, _) = deploy_syscall( diff --git a/listings/getting-started/errors/src/vault_errors.cairo b/listings/getting-started/errors/src/vault_errors.cairo index 4f34b951..20c230c8 100644 --- a/listings/getting-started/errors/src/vault_errors.cairo +++ b/listings/getting-started/errors/src/vault_errors.cairo @@ -7,11 +7,12 @@ pub trait IVaultErrorsExample { // ANCHOR: contract pub mod VaultErrors { pub const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance'; -// you can define more errors here + // you can define more errors here } #[starknet::contract] pub mod VaultErrorsExample { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; use super::VaultErrors; #[storage] @@ -50,7 +51,7 @@ mod test { use super::{ VaultErrorsExample, IVaultErrorsExampleDispatcher, IVaultErrorsExampleDispatcherTrait }; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; fn deploy() -> IVaultErrorsExampleDispatcher { let (contract_address, _) = deploy_syscall( diff --git a/listings/getting-started/events/Scarb.toml b/listings/getting-started/events/Scarb.toml index 7c248e1f..58d641fa 100644 --- a/listings/getting-started/events/Scarb.toml +++ b/listings/getting-started/events/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "events" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/events/src/counter.cairo b/listings/getting-started/events/src/counter.cairo index 10d879a6..8b71d774 100644 --- a/listings/getting-started/events/src/counter.cairo +++ b/listings/getting-started/events/src/counter.cairo @@ -7,11 +7,12 @@ pub trait IEventCounter { #[starknet::contract] pub mod EventCounter { use starknet::{get_caller_address, ContractAddress}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { // Counter value - counter: u128, + pub counter: u128, } #[event] @@ -54,7 +55,7 @@ pub mod EventCounter { } ) ); - // ANCHOR_END: emit + // ANCHOR_END: emit } } } @@ -63,16 +64,12 @@ pub mod EventCounter { #[cfg(test)] mod tests { use super::{ - EventCounter, - EventCounter::{ - counterContractMemberStateTrait, Event, CounterIncreased, UserIncreaseCounter - }, + EventCounter, EventCounter::{Event, CounterIncreased, UserIncreaseCounter}, IEventCounterDispatcherTrait, IEventCounterDispatcher }; - use starknet::{ - ContractAddress, contract_address_const, SyscallResultTrait, syscalls::deploy_syscall - }; - use starknet::testing::{set_contract_address, set_account_contract_address}; + use starknet::{contract_address_const, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::testing::set_contract_address; + use starknet::storage::StoragePointerReadAccess; #[test] fn test_increment_events() { @@ -81,7 +78,7 @@ mod tests { ) .unwrap_syscall(); let mut contract = IEventCounterDispatcher { contract_address }; - let state = EventCounter::contract_state_for_testing(); + let state = @EventCounter::contract_state_for_testing(); let amount = 10; let caller = contract_address_const::<'caller'>(); diff --git a/listings/getting-started/factory/Scarb.toml b/listings/getting-started/factory/Scarb.toml index 23664f2c..5df9fd4c 100644 --- a/listings/getting-started/factory/Scarb.toml +++ b/listings/getting-started/factory/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "factory" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/factory/src/simple_factory.cairo b/listings/getting-started/factory/src/simple_factory.cairo index 83e6f22f..6b6452d7 100644 --- a/listings/getting-started/factory/src/simple_factory.cairo +++ b/listings/getting-started/factory/src/simple_factory.cairo @@ -19,6 +19,7 @@ pub trait ICounterFactory { #[starknet::contract] pub mod CounterFactory { use starknet::{ContractAddress, ClassHash, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { @@ -69,10 +70,7 @@ pub mod CounterFactory { #[cfg(test)] mod tests { use super::{CounterFactory, ICounterFactoryDispatcher, ICounterFactoryDispatcherTrait}; - use starknet::{ - SyscallResultTrait, ContractAddress, ClassHash, contract_address_const, - syscalls::deploy_syscall - }; + use starknet::{SyscallResultTrait, ClassHash, syscalls::deploy_syscall}; // Define a target contract to deploy mod target { @@ -85,6 +83,8 @@ mod tests { #[starknet::contract] pub mod SimpleCounter { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { // Counter variable @@ -116,7 +116,7 @@ mod tests { } } } - use target::{ISimpleCounterDispatcher, ISimpleCounterDispatcherTrait}; + use target::ISimpleCounterDispatcherTrait; /// Deploy a counter factory contract fn deploy_factory( diff --git a/listings/getting-started/interfaces_traits/Scarb.toml b/listings/getting-started/interfaces_traits/Scarb.toml index 4aa5e26d..94a47b46 100644 --- a/listings/getting-started/interfaces_traits/Scarb.toml +++ b/listings/getting-started/interfaces_traits/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "interfaces_traits" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/interfaces_traits/src/explicit.cairo b/listings/getting-started/interfaces_traits/src/explicit.cairo index d31bd219..0c9b109e 100644 --- a/listings/getting-started/interfaces_traits/src/explicit.cairo +++ b/listings/getting-started/interfaces_traits/src/explicit.cairo @@ -7,6 +7,8 @@ pub trait IExplicitInterfaceContract { #[starknet::contract] pub mod ExplicitInterfaceContract { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { value: u32 @@ -28,10 +30,10 @@ pub mod ExplicitInterfaceContract { #[cfg(test)] mod tests { use super::{ - IExplicitInterfaceContract, ExplicitInterfaceContract, IExplicitInterfaceContractDispatcher, + ExplicitInterfaceContract, IExplicitInterfaceContractDispatcher, IExplicitInterfaceContractDispatcherTrait }; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; #[test] fn test_interface() { diff --git a/listings/getting-started/interfaces_traits/src/implicit.cairo b/listings/getting-started/interfaces_traits/src/implicit.cairo index 47e8e5f3..15861ba5 100644 --- a/listings/getting-started/interfaces_traits/src/implicit.cairo +++ b/listings/getting-started/interfaces_traits/src/implicit.cairo @@ -1,6 +1,8 @@ // ANCHOR: contract #[starknet::contract] pub mod ImplicitInterfaceContract { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { value: u32 @@ -24,13 +26,8 @@ pub mod ImplicitInterfaceContract { #[cfg(test)] mod tests { - use super::{ - ImplicitInterfaceContract, ImplicitInterfaceContract::valueContractMemberStateTrait, - ImplicitInterfaceContract::IImplicitInterfaceContract - }; - use starknet::{ - ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, testing::set_contract_address - }; + use super::{ImplicitInterfaceContract, ImplicitInterfaceContract::IImplicitInterfaceContract}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall, testing::set_contract_address}; #[test] fn test_interface() { diff --git a/listings/getting-started/interfaces_traits/src/implicit_internal.cairo b/listings/getting-started/interfaces_traits/src/implicit_internal.cairo index a10cf8f6..2dc83f15 100644 --- a/listings/getting-started/interfaces_traits/src/implicit_internal.cairo +++ b/listings/getting-started/interfaces_traits/src/implicit_internal.cairo @@ -8,6 +8,8 @@ pub trait IImplicitInternalContract { #[starknet::contract] pub mod ImplicitInternalContract { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { value: u32 @@ -49,10 +51,10 @@ pub mod ImplicitInternalContract { #[cfg(test)] mod tests { use super::{ - IImplicitInternalContract, ImplicitInternalContract, IImplicitInternalContractDispatcher, + ImplicitInternalContract, IImplicitInternalContractDispatcher, IImplicitInternalContractDispatcherTrait }; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; #[test] fn test_interface() { diff --git a/listings/getting-started/mappings/Scarb.toml b/listings/getting-started/mappings/Scarb.toml index 796fdbc2..a1f50323 100644 --- a/listings/getting-started/mappings/Scarb.toml +++ b/listings/getting-started/mappings/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "mappings" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test = "2.7.1" + [scripts] test.workspace = true diff --git a/listings/getting-started/mappings/src/mappings.cairo b/listings/getting-started/mappings/src/mappings.cairo index 5fd4423e..802152cc 100644 --- a/listings/getting-started/mappings/src/mappings.cairo +++ b/listings/getting-started/mappings/src/mappings.cairo @@ -10,11 +10,11 @@ pub trait IMapContract { #[starknet::contract] pub mod MapContract { use starknet::ContractAddress; + use starknet::storage::{Map, StorageMapReadAccess, StorageMapWriteAccess}; #[storage] struct Storage { - // The `LegacyMap` type is only available inside the `Storage` struct. - map: LegacyMap::, + map: Map::, } #[abi(embed_v0)] @@ -33,7 +33,7 @@ pub mod MapContract { #[cfg(test)] mod test { use super::{MapContract, IMapContractDispatcher, IMapContractDispatcherTrait}; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; #[test] fn test_deploy_and_set_get() { diff --git a/listings/getting-started/storage/Scarb.toml b/listings/getting-started/storage/Scarb.toml index e912d31b..caf77195 100644 --- a/listings/getting-started/storage/Scarb.toml +++ b/listings/getting-started/storage/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "storage" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/storage/src/contract.cairo b/listings/getting-started/storage/src/contract.cairo index 44ec565c..75d734a5 100644 --- a/listings/getting-started/storage/src/contract.cairo +++ b/listings/getting-started/storage/src/contract.cairo @@ -3,9 +3,9 @@ pub mod Contract { #[storage] struct Storage { - a: u128, - b: u8, - c: u256 + pub a: u128, + pub b: u8, + pub c: u256 } } // ANCHOR_END: contract @@ -14,9 +14,7 @@ pub mod Contract { mod test { use super::Contract; use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; - use storage::contract::Contract::{ - aContractMemberStateTrait, bContractMemberStateTrait, cContractMemberStateTrait - }; + use starknet::storage::StoragePointerReadAccess; #[test] fn test_can_deploy() { @@ -28,7 +26,7 @@ mod test { #[test] fn test_storage_members() { - let state = Contract::contract_state_for_testing(); + let state = @Contract::contract_state_for_testing(); assert_eq!(state.a.read(), 0_u128); assert_eq!(state.b.read(), 0_u8); assert_eq!(state.c.read(), 0_u256); diff --git a/listings/getting-started/storage/src/minimal_contract.cairo b/listings/getting-started/storage/src/minimal_contract.cairo index 28128fc7..22bb7ccc 100644 --- a/listings/getting-started/storage/src/minimal_contract.cairo +++ b/listings/getting-started/storage/src/minimal_contract.cairo @@ -17,7 +17,7 @@ mod test { Contract::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false ) .unwrap_syscall(); - // Not much to test + // Not much to test } } diff --git a/listings/getting-started/storing_custom_types/Scarb.toml b/listings/getting-started/storing_custom_types/Scarb.toml index f41505d2..4a949d4f 100644 --- a/listings/getting-started/storing_custom_types/Scarb.toml +++ b/listings/getting-started/storing_custom_types/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "storing_custom_types" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/storing_custom_types/src/contract.cairo b/listings/getting-started/storing_custom_types/src/contract.cairo index f3c80c98..f786a9be 100644 --- a/listings/getting-started/storing_custom_types/src/contract.cairo +++ b/listings/getting-started/storing_custom_types/src/contract.cairo @@ -1,6 +1,7 @@ #[starknet::interface] pub trait IStoringCustomType { fn set_person(ref self: TContractState, person: Person); + fn set_name(ref self: TContractState, name: felt252); } // ANCHOR: contract @@ -14,10 +15,11 @@ pub struct Person { #[starknet::contract] pub mod StoringCustomType { + use starknet::storage::StoragePointerWriteAccess; use super::Person; #[storage] - pub struct Storage { + struct Storage { pub person: Person } @@ -26,16 +28,20 @@ pub mod StoringCustomType { fn set_person(ref self: ContractState, person: Person) { self.person.write(person); } + + // ANCHOR: set_name + fn set_name(ref self: ContractState, name: felt252) { + self.person.name.write(name); + } + // ANCHOR_END: set_name } } // ANCHOR_END: contract #[cfg(test)] mod tests { - use super::{ - IStoringCustomType, StoringCustomType, Person, - StoringCustomType::personContractMemberStateTrait - }; + use super::{IStoringCustomType, StoringCustomType, Person,}; + use starknet::storage::StoragePointerReadAccess; #[test] fn can_call_set_person() { @@ -44,9 +50,20 @@ mod tests { let person = Person { age: 10, name: 'Joe' }; state.set_person(person); + let read_person = state.person.read(); - assert(person.age == read_person.age, 'wrong age'); - assert(person.name == read_person.name, 'wrong name'); + assert_eq!(person.age, read_person.age); + assert_eq!(person.name, read_person.name); + } + + #[test] + fn can_call_set_name() { + let mut state = StoringCustomType::contract_state_for_testing(); + + state.set_name('John'); + + let read_person = state.person.read(); + assert_eq!(read_person.name, 'John'); } } diff --git a/listings/getting-started/testing_how_to/Scarb.toml b/listings/getting-started/testing_how_to/Scarb.toml index adc0bb3b..5046ee3d 100644 --- a/listings/getting-started/testing_how_to/Scarb.toml +++ b/listings/getting-started/testing_how_to/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "testing_how_to" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/testing_how_to/src/contract.cairo b/listings/getting-started/testing_how_to/src/contract.cairo index 6eab76dd..4d125cab 100644 --- a/listings/getting-started/testing_how_to/src/contract.cairo +++ b/listings/getting-started/testing_how_to/src/contract.cairo @@ -9,11 +9,12 @@ pub trait ISimpleContract { #[starknet::contract] pub mod SimpleContract { use starknet::{get_caller_address, ContractAddress}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[storage] struct Storage { - value: u32, - owner: ContractAddress + pub value: u32, + pub owner: ContractAddress } #[constructor] @@ -48,9 +49,7 @@ mod tests { // Import the deploy syscall to be able to deploy the contract. use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; - use starknet::{ - ContractAddress, get_caller_address, get_contract_address, contract_address_const - }; + use starknet::{get_contract_address, contract_address_const}; // Use starknet test utils to fake the contract_address use starknet::testing::set_contract_address; @@ -111,7 +110,7 @@ mod tests { // As the current caller is not the owner, the value cannot be set. let new_value: u32 = 20; contract.set_value(new_value); - // Panic expected + // Panic expected } #[test] @@ -125,18 +124,14 @@ mod tests { // ANCHOR: tests_with_state #[cfg(test)] mod tests_with_states { - // Only import the contract + // Only import the contract and implementation use super::SimpleContract; - - // For accessing storage variables and entrypoints, - // we must import the contract member state traits and implementation. - use SimpleContract::{ - SimpleContractImpl, valueContractMemberStateTrait, ownerContractMemberStateTrait - }; + use SimpleContract::SimpleContractImpl; use starknet::contract_address_const; use starknet::testing::set_caller_address; use core::num::traits::Zero; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; #[test] fn test_standalone_state() { @@ -145,7 +140,7 @@ mod tests_with_states { // As no contract was deployed, the constructor was not called on the state // - with valueContractMemberStateTrait assert_eq!(state.value.read(), 0); - // - with SimpleContractImpl + // - with SimpleContractImpl assert_eq!(state.get_value(), 0); assert_eq!(state.owner.read(), Zero::zero()); @@ -169,9 +164,7 @@ mod tests_with_states { // But we can also deploy the contract and interact with it using the dispatcher // as shown in the previous tests, and still use the state for testing. use super::{ISimpleContractDispatcher, ISimpleContractDispatcherTrait}; - use starknet::{ - ContractAddress, SyscallResultTrait, syscalls::deploy_syscall, testing::set_contract_address - }; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall, testing::set_contract_address}; #[test] fn test_state_with_contract() { diff --git a/listings/getting-started/variables/Scarb.toml b/listings/getting-started/variables/Scarb.toml index 32b7ab20..4b6f31b2 100644 --- a/listings/getting-started/variables/Scarb.toml +++ b/listings/getting-started/variables/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "variables" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/variables/src/global_variables.cairo b/listings/getting-started/variables/src/global_variables.cairo index 1389bdaf..1e808e50 100644 --- a/listings/getting-started/variables/src/global_variables.cairo +++ b/listings/getting-started/variables/src/global_variables.cairo @@ -17,7 +17,7 @@ pub mod GlobalExample { fn foo(ref self: ContractState) { // Call the get_caller_address function to get the sender address let _caller = get_caller_address(); - // ... + // ... } } } @@ -34,6 +34,6 @@ mod test { GlobalExample::TEST_CLASS_HASH.try_into().unwrap(), 0, array![].span(), false ) .unwrap_syscall(); - // Not much to test + // Not much to test } } diff --git a/listings/getting-started/variables/src/local_variables.cairo b/listings/getting-started/variables/src/local_variables.cairo index 2bcd195d..4b2bcac5 100644 --- a/listings/getting-started/variables/src/local_variables.cairo +++ b/listings/getting-started/variables/src/local_variables.cairo @@ -22,7 +22,7 @@ pub mod LocalVariablesExample { let sum = value + increment; sum } - // We can't access the variable `sum` here, as it's out of scope. + // We can't access the variable `sum` here, as it's out of scope. } } } diff --git a/listings/getting-started/variables/src/storage_variables.cairo b/listings/getting-started/variables/src/storage_variables.cairo index e5ff96ea..864c9175 100644 --- a/listings/getting-started/variables/src/storage_variables.cairo +++ b/listings/getting-started/variables/src/storage_variables.cairo @@ -7,12 +7,15 @@ pub trait IStorageVariableExample { // ANCHOR: contract #[starknet::contract] pub mod StorageVariablesExample { + // You need to import these storage functions to read and write to storage variables + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + // All storage variables are contained in a struct called Storage // annotated with the `#[storage]` attribute #[storage] struct Storage { // Storage variable holding a number - value: u32 + pub value: u32 } #[abi(embed_v0)] @@ -34,11 +37,12 @@ pub mod StorageVariablesExample { #[cfg(test)] mod test { use super::{ - StorageVariablesExample, StorageVariablesExample::valueContractMemberStateTrait, - IStorageVariableExampleDispatcher, IStorageVariableExampleDispatcherTrait + StorageVariablesExample, IStorageVariableExampleDispatcher, + IStorageVariableExampleDispatcherTrait }; use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; use starknet::testing::set_contract_address; + use starknet::storage::StoragePointerReadAccess; #[test] fn test_can_deploy_and_mutate_storage() { @@ -55,7 +59,7 @@ mod test { assert_eq!(contract.get(), initial_value); // With contract state directly - let state = StorageVariablesExample::contract_state_for_testing(); + let state = @StorageVariablesExample::contract_state_for_testing(); set_contract_address(contract_address); assert_eq!(state.value.read(), initial_value); } diff --git a/listings/getting-started/visibility/Scarb.toml b/listings/getting-started/visibility/Scarb.toml index f589f9f5..aeadb2ef 100644 --- a/listings/getting-started/visibility/Scarb.toml +++ b/listings/getting-started/visibility/Scarb.toml @@ -1,11 +1,14 @@ [package] name = "visibility" version.workspace = true -edition = '2023_11' +edition = "2024_07" [dependencies] starknet.workspace = true +[dev-dependencies] +cairo_test.workspace = true + [scripts] test.workspace = true diff --git a/listings/getting-started/visibility/src/visibility.cairo b/listings/getting-started/visibility/src/visibility.cairo index 25256830..240b6a6c 100644 --- a/listings/getting-started/visibility/src/visibility.cairo +++ b/listings/getting-started/visibility/src/visibility.cairo @@ -7,9 +7,11 @@ pub trait IExampleContract { // ANCHOR: contract #[starknet::contract] pub mod ExampleContract { + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; + #[storage] struct Storage { - value: u32 + pub value: u32 } // The `#[abi(embed_v0)]` attribute indicates that all @@ -55,11 +57,10 @@ pub mod ExampleContract { #[cfg(test)] mod test { use super::{ExampleContract, IExampleContractDispatcher, IExampleContractDispatcherTrait}; - use starknet::{ContractAddress, SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::{SyscallResultTrait, syscalls::deploy_syscall}; + use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess}; // These imports will allow us to directly access and set the contract state: - // - for `value` storage variable access - use super::ExampleContract::valueContractMemberStateTrait; // - for `PrivateFunctionsTrait` internal functions access // implementation need to be public to be able to access it use super::ExampleContract::PrivateFunctionsTrait; diff --git a/listings/templates/scarb/.gitignore b/listings/templates/scarb/.gitignore deleted file mode 100644 index eb5a316c..00000000 --- a/listings/templates/scarb/.gitignore +++ /dev/null @@ -1 +0,0 @@ -target diff --git a/listings/templates/scarb/Scarb.toml b/listings/templates/scarb/Scarb.toml deleted file mode 100644 index 95266f84..00000000 --- a/listings/templates/scarb/Scarb.toml +++ /dev/null @@ -1,12 +0,0 @@ -[package] -name = "scarb" -version.workspace = true -edition = '2023_11' - -[dependencies] -starknet.workspace = true - -[scripts] -test.workspace = true - -[[target.starknet-contract]] diff --git a/listings/templates/scarb/src/lib.cairo b/listings/templates/scarb/src/lib.cairo deleted file mode 100644 index bf218399..00000000 --- a/listings/templates/scarb/src/lib.cairo +++ /dev/null @@ -1,28 +0,0 @@ -fn main() -> felt252 { - fib(16) -} - -fn fib(mut n: felt252) -> felt252 { - let mut a: felt252 = 0; - let mut b: felt252 = 1; - loop { - if n == 0 { - break a; - } - n = n - 1; - let temp = b; - b = a + b; - a = temp; - } -} - -#[cfg(test)] -mod tests { - use super::fib; - - #[test] - #[available_gas(100000)] - fn it_works() { - assert(fib(16) == 987, 'it works!'); - } -} diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 4ddbf67a..a74bd1cd 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -30,7 +30,7 @@ Summary - [Testing contracts](./getting-started/testing/contract-testing.md) - [Cairo cheatsheet](./getting-started/cairo_cheatsheet/cairo_cheatsheet.md) - [Felt](./getting-started/cairo_cheatsheet/felt.md) - - [LegacyMap](./getting-started/cairo_cheatsheet/mapping.md) + - [Map](./getting-started/cairo_cheatsheet/mapping.md) - [Arrays](./getting-started/cairo_cheatsheet/arrays.md) - [loop](./getting-started/cairo_cheatsheet/loop.md) - [while](./getting-started/cairo_cheatsheet/while.md) @@ -70,7 +70,6 @@ Summary # Advanced concepts - [Writing to any storage slot](./advanced-concepts/write_to_any_slot.md) -- [Storing Arrays](./advanced-concepts/storing_arrays.md) - [Struct as mapping key](./advanced-concepts/struct-mapping-key.md) - [Hashing](./advanced-concepts/hashing.md) @@ -79,7 +78,6 @@ Summary - [Storage Optimisations](./advanced-concepts/optimisations/store_using_packing.md) - [Account Abstraction](./advanced-concepts/account_abstraction/index.md) - [Account Contract](./advanced-concepts/account_abstraction/account_contract.md) -- [List](./advanced-concepts/list.md) - [Library Calls](./advanced-concepts/library_calls.md) - [Plugins](./advanced-concepts/plugins.md) - [Signature Verification](./advanced-concepts/signature_verification.md) diff --git a/src/advanced-concepts/list.md b/src/advanced-concepts/list.md deleted file mode 100644 index 3bcc5fa1..00000000 --- a/src/advanced-concepts/list.md +++ /dev/null @@ -1,68 +0,0 @@ -# List - -By default, there is no list type supported in Cairo, but you can use the Alexandria standard library. You can refer to the [Alexandria documentation](https://github.com/keep-starknet-strange/alexandria/tree/main/packages/storage) for more details. - -## What is `List`? - -An ordered sequence of values that can be used in Starknet storage: - -```rust -#[storage] -struct Storage { - amounts: List -} -``` - -### Interface - -```rust -trait ListTrait { - fn len(self: @List) -> u32; - fn is_empty(self: @List) -> bool; - fn append(ref self: List, value: T) -> u32; - fn get(self: @List, index: u32) -> Option; - fn set(ref self: List, index: u32, value: T); - fn pop_front(ref self: List) -> Option; - fn array(self: @List) -> Array; -} -``` - -`List` also implements `IndexView` so you can use the familiar bracket notation to access its members: - -```rust -let second = self.amounts.read()[1]; -``` - -Note that unlike `get`, using this bracket notation panics when accessing an out of bounds index. - -### Support for custom types - -`List` supports most of the corelib types out of the box. If you want to store your own custom type in a `List`, you have to implement the `Store` trait for it. You can have the compiler derive it for you using the `#[derive(starknet::Store)]` attribute. - -### Caveats - -There are two idiosyncrasies you should be aware of when using `List`: - -1. The `append` operation costs 2 storage writes - one for the value itself, and another one for updating the List's length -2. Due to a compiler limitation, it is not possible to use mutating operations with a single inline statement. For example, `self.amounts.read().append(42);` will not work. You have to do it in 2 steps: - -```rust -let mut amounts = self.amounts.read(); -amounts.append(42); -``` - -### Dependencies - -Update your project dependencies in the `Scarb.toml` file: - -```rust -[dependencies] -(...) -alexandria_storage = { git = "https://github.com/keep-starknet-strange/alexandria.git" } -``` - -For example, let's use `List` to create a contract that tracks a list of amounts and tasks: - -```rust -{{#include ../../listings/advanced-concepts/using_lists/src/contract.cairo}} -``` diff --git a/src/advanced-concepts/storing_arrays.md b/src/advanced-concepts/storing_arrays.md deleted file mode 100644 index b819dde9..00000000 --- a/src/advanced-concepts/storing_arrays.md +++ /dev/null @@ -1,19 +0,0 @@ -# Storing Arrays - -On Starknet, complex values (e.g. tuples or structs) are stored in a continuous segment starting from the address of the storage variable. There is a limitation in Cairo that restricts complex storage values to a maximum of 256 field elements. This means that to store arrays with more than 255 elements, you would have to split them into segments of size `n <= 255` and store these segments at multiple storage addresses. There is currently no native support for storing arrays in Cairo, so you would need to write your own implementation of the `Store` trait for the array type you wish to store. - -However, the `ByteArray` struct can be used to store `Array` in storage without additional implementation. Before implementing your own `Store` trait, consider wether the `ByteArray` struct can be used to store the data you need! See the [ByteArray](../getting-started/basics/bytearrays-strings.md#bytearray-long-strings) section for more information. - -> Note: While storing arrays in storage is possible, it is not always recommended, as the read and write operations can get very costly. For example, reading an array of size `n` requires `n` storage reads, and writing to an array of size `n` requires `n` storage writes. If you only need to access a single element of the array at a time, it is recommended to use a `LegacyMap` and store the length in another variable instead. - -The following example demonstrates how to write a simple implementation of the `StorageAccess` trait for the `Array` type, allowing us to store arrays of up to 255 `felt252` elements. - -```rust -{{#include ../../listings/advanced-concepts/storing_arrays/src/contract.cairo:StorageAccessImpl}} -``` - -You can then import this implementation in your contract and use it to store arrays in storage: - -```rust -{{#include ../../listings/advanced-concepts/storing_arrays/src/contract.cairo:StoreArrayContract}} -``` diff --git a/src/advanced-concepts/struct-mapping-key.md b/src/advanced-concepts/struct-mapping-key.md index 9160c2cb..72773110 100644 --- a/src/advanced-concepts/struct-mapping-key.md +++ b/src/advanced-concepts/struct-mapping-key.md @@ -1,9 +1,9 @@ # Structs as mapping keys -In order to use structs as mapping keys, you can use `#[derive(Hash)]` on the struct definition. This will automatically generate a hash function for the struct that can be used to represent the struct as a key in a `LegacyMap`. +In order to use structs as mapping keys, you can use `#[derive(Hash)]` on the struct definition. This will automatically generate a hash function for the struct that can be used to represent the struct as a key in a `Map`. Consider the following example in which we would like to use an object of -type `Pet` as a key in a `LegacyMap`. The `Pet` struct has three fields: `name`, `age` and `owner`. We consider that the combination of these three fields uniquely identifies a pet. +type `Pet` as a key in a `Map`. The `Pet` struct has three fields: `name`, `age` and `owner`. We consider that the combination of these three fields uniquely identifies a pet. ```rust {{#include ../../listings/advanced-concepts/struct_as_mapping_key/src/contract.cairo}} diff --git a/src/applications/crowdfunding.md b/src/applications/crowdfunding.md index 8d78f05a..5257a78c 100644 --- a/src/applications/crowdfunding.md +++ b/src/applications/crowdfunding.md @@ -12,7 +12,7 @@ Crowdfunding is a method of raising capital through the collective effort of man 8. The creator can at any point cancel the campaign for whatever reason and refund all of the pledgers. 9. The contract admin can upgrade the contract implementation, refunding all of the users and reseting the campaign state (we will use this in the [Advanced Factory chapter](./advanced_factory.md)). -Because contract upgrades need to be able to refund all of the pledges, we need to be able to iterate over all of the pledgers and their amounts. Since iteration is not supported by `LegacyMap`, we need to create a custom storage type that will encompass pledge management. We use a component for this purpose. +Because contract upgrades need to be able to refund all of the pledges, we need to be able to iterate over all of the pledgers and their amounts. Since iteration is not supported by `Map`, we need to create a custom storage type that will encompass pledge management. We use a component for this purpose. ```rust {{#include ../../listings/applications/crowdfunding/src/campaign/pledgeable.cairo:component}} diff --git a/src/getting-started/basics/mappings.md b/src/getting-started/basics/mappings.md index db0b6825..d52a87f7 100644 --- a/src/getting-started/basics/mappings.md +++ b/src/getting-started/basics/mappings.md @@ -1,12 +1,12 @@ # Mappings -Maps are a key-value data structure used to store data within a smart contract. In Cairo they are implemented using the `LegacyMap` type. It's important to note that the `LegacyMap` type can only be used inside the `Storage` struct of a contract and that it can't be used elsewhere. +Maps are a key-value data structure used to store data within a smart contract. In Cairo they are implemented using the `Map` type. It's important to note that the `Map` type can only be used inside the `Storage` struct of a contract and that it can't be used elsewhere. -Here we demonstrate how to use the `LegacyMap` type within a Cairo contract, to map between a key of type `ContractAddress` and value of type `felt252`. The key-value types are specified within angular brackets <>. We write to the map by calling the `write()` method, passing in both the key and value. Similarly, we can read the value associated with a given key by calling the `read()` method and passing in the relevant key. +Here we demonstrate how to use the `Map` type within a Cairo contract, to map between a key of type `ContractAddress` and value of type `felt252`. The key-value types are specified within angular brackets <>. We write to the map by calling the `write()` method, passing in both the key and value. Similarly, we can read the value associated with a given key by calling the `read()` method and passing in the relevant key. Some additional notes: -- More complex key-value mappings are possible, for example we could use `LegacyMap::<(ContractAddress, ContractAddress), felt252>` to create an allowance on an ERC20 token contract. +- More complex key-value mappings are possible, for example we could use `Map::<(ContractAddress, ContractAddress), felt252>` to create an allowance on an ERC20 token contract. - In mappings, the address of the value at key `k_1,...,k_n` is `h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)` where `ℎ` is the Pedersen hash and the final value is taken \\( \bmod {2^{251}} - 256 \\). You can learn more about the contract storage layout in the [Starknet Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-storage/#storage_variables). diff --git a/src/getting-started/basics/storing-custom-types.md b/src/getting-started/basics/storing-custom-types.md index ed96f088..e46c7baa 100644 --- a/src/getting-started/basics/storing-custom-types.md +++ b/src/getting-started/basics/storing-custom-types.md @@ -5,3 +5,10 @@ While native types can be stored in a contract's storage without any additional ```rust {{#rustdoc_include ../../../listings/getting-started/storing_custom_types/src/contract.cairo:contract}} ``` + +Note that it is also possible to individually access the members of the stored struct. +This is possible because deriving the `Store` trait also generates the corresponding `StoragePointer` for each member. + +```rust +{{#rustdoc_include ../../../listings/getting-started/storing_custom_types/src/contract.cairo:set_name}} +``` diff --git a/src/getting-started/cairo_cheatsheet/mapping.md b/src/getting-started/cairo_cheatsheet/mapping.md index b9ece39e..fee30ec6 100644 --- a/src/getting-started/cairo_cheatsheet/mapping.md +++ b/src/getting-started/cairo_cheatsheet/mapping.md @@ -1,6 +1,6 @@ -# `LegacyMap` +# `Map` -The `LegacyMap` type can be used to represent a collection of key-value. +The `Map` type can be used to represent a collection of key-value. ```rust {{#include ../../../listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo}} diff --git a/src/starknet-by-example.md b/src/starknet-by-example.md index 962109c8..f1cff8ba 100644 --- a/src/starknet-by-example.md +++ b/src/starknet-by-example.md @@ -36,7 +36,8 @@ For more resources, check [Awesome Starknet](https://github.com/keep-starknet-st The current version this book uses: ``` -cairo 2.6.3 -edition = '2023_11' +cairo 2.8.2 +edition = "2024_07" +sierra: 1.6.0 {{#include ../.tool-versions}} ``` From 1e225883563f1e72849912834ee8cd0d874818f6 Mon Sep 17 00:00:00 2001 From: Nenad Date: Tue, 1 Oct 2024 09:42:22 +0200 Subject: [PATCH 31/36] Random Number Generator (#238) * feat: dice game vrf application * feat: add summary nav * fix: ran scarb fmt * fix: ran scarb fmt * Fix new lines * Add more info on randomness sources * Rename dice_game_vrf.md->random_number_generator.md and update titles * minor rewording of 1 entropy source * remove anchors * Minor changes to fn names * Implement dice game scaffold * Implement Pragma randomness * minor refactor in randomness request * Implement powerball scaffold * Turn Dice Game into CoinFlip * Implement coin_flip test * Add more tests * Update titles * Remove redundant blank line * Add premium fee calculation into tests * Assert leftover balance * Remove comment about fees * Increase the expected callback fee, update mock to expose fee calc fn * Unfinished: refunded * Store and use is_refunded flag * Implement logic necessary to successfully perform & test refund * Update callback fee limit based on manual testing + update term to deposit * Format * Use a FlipData struct instead of tuple * Fix refund * Simplify CoinFlip to pay the flips itself * CALLBACK_FEE_DEPOSIT->MAX_CALLBACK_FEE_DEPOSIT * Update tests to test the new CoinFlip contract * Fix compile errors * Increase publish_delay to 1 & remove unused imports * Remove starkli-wallet dir * Generate 3 random words for the 1st test * refactor tests * Add missng newline to scarb.toml * fix typo in md * reword 'manipulation' def * Chainlink->Pragma * link to Commit-reveal chapter issue * list 'shut down' as possible centr. issue with ext. oracles * Turn point 5 into a note * Remove Sideways enum * add contract description * Remove ResultTrait from crowdfunding tests.cairo --------- Co-authored-by: Tony Stark Co-authored-by: Nenad Co-authored-by: Nenad --- .gitignore | 1 + Scarb.lock | 14 + Scarb.toml | 1 + .../advanced_factory/src/tests.cairo | 2 - listings/applications/coin_flip/.gitignore | 2 + listings/applications/coin_flip/Scarb.toml | 21 ++ .../applications/coin_flip/src/contract.cairo | 164 ++++++++++ listings/applications/coin_flip/src/lib.cairo | 5 + .../coin_flip/src/mock_randomness.cairo | 166 ++++++++++ .../applications/coin_flip/src/tests.cairo | 287 ++++++++++++++++++ .../applications/crowdfunding/src/tests.cairo | 1 - src/SUMMARY.md | 1 + src/applications/random_number_generator.md | 64 ++++ 13 files changed, 726 insertions(+), 3 deletions(-) create mode 100644 listings/applications/coin_flip/.gitignore create mode 100644 listings/applications/coin_flip/Scarb.toml create mode 100644 listings/applications/coin_flip/src/contract.cairo create mode 100644 listings/applications/coin_flip/src/lib.cairo create mode 100644 listings/applications/coin_flip/src/mock_randomness.cairo create mode 100644 listings/applications/coin_flip/src/tests.cairo create mode 100644 src/applications/random_number_generator.md diff --git a/.gitignore b/.gitignore index 41187080..72306c0f 100644 --- a/.gitignore +++ b/.gitignore @@ -9,3 +9,4 @@ output # Others .snfoundry_cache .vscode/settings.json +**/starkli-wallet diff --git a/Scarb.lock b/Scarb.lock index ee2add0e..2aaddaf7 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -22,6 +22,15 @@ version = "0.1.0" name = "calling_other_contracts" version = "0.1.0" +[[package]] +name = "coin_flip" +version = "0.1.0" +dependencies = [ + "openzeppelin", + "pragma_lib", + "snforge_std", +] + [[package]] name = "components" version = "0.1.0" @@ -206,6 +215,11 @@ version = "0.16.0" source = "registry+https://scarbs.xyz/" checksum = "sha256:a494aeb5f1371db7f22e922196aa41d1d1698877a766a838350c0b6ffe49fda2" +[[package]] +name = "pragma_lib" +version = "1.0.0" +source = "git+https://github.com/astraly-labs/pragma-lib#86d7ccdc15b349b8b48d9796fc8464c947bea6e1" + [[package]] name = "simple_account" version = "0.1.0" diff --git a/Scarb.toml b/Scarb.toml index f8dd7cba..7482a04e 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -18,6 +18,7 @@ assert_macros = "2.8.2" snforge_std = "0.30.0" openzeppelin = "0.16.0" components = { path = "listings/applications/components" } +pragma_lib = { git = "https://github.com/astraly-labs/pragma-lib" } [workspace.package] description = "Collection of examples of how to use the Cairo programming language to create smart contracts on Starknet." diff --git a/listings/applications/advanced_factory/src/tests.cairo b/listings/applications/advanced_factory/src/tests.cairo index 759be3d0..41588040 100644 --- a/listings/applications/advanced_factory/src/tests.cairo +++ b/listings/applications/advanced_factory/src/tests.cairo @@ -1,5 +1,3 @@ -use core::clone::Clone; -use core::result::ResultTrait; use advanced_factory::contract::{ CampaignFactory, ICampaignFactoryDispatcher, ICampaignFactoryDispatcherTrait }; diff --git a/listings/applications/coin_flip/.gitignore b/listings/applications/coin_flip/.gitignore new file mode 100644 index 00000000..73aa31e6 --- /dev/null +++ b/listings/applications/coin_flip/.gitignore @@ -0,0 +1,2 @@ +target +.snfoundry_cache/ diff --git a/listings/applications/coin_flip/Scarb.toml b/listings/applications/coin_flip/Scarb.toml new file mode 100644 index 00000000..c1ae15f7 --- /dev/null +++ b/listings/applications/coin_flip/Scarb.toml @@ -0,0 +1,21 @@ +[package] +name = "coin_flip" +version.workspace = true +edition = "2024_07" + +[lib] + +[dependencies] +starknet.workspace = true +openzeppelin.workspace = true +pragma_lib.workspace = true +snforge_std.workspace = true + +[dev-dependencies] +assert_macros.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] +build-external-contracts = ["openzeppelin_presets::erc20::ERC20Upgradeable"] diff --git a/listings/applications/coin_flip/src/contract.cairo b/listings/applications/coin_flip/src/contract.cairo new file mode 100644 index 00000000..cefe052e --- /dev/null +++ b/listings/applications/coin_flip/src/contract.cairo @@ -0,0 +1,164 @@ +use starknet::ContractAddress; + +#[starknet::interface] +pub trait ICoinFlip { + fn flip(ref self: TContractState); +} + +// declares just the pragma_lib::abi::IRandomness.receive_random_words function +#[starknet::interface] +pub trait IPragmaVRF { + fn receive_random_words( + ref self: TContractState, + requestor_address: ContractAddress, + request_id: u64, + random_words: Span, + calldata: Array + ); +} + +#[starknet::contract] +pub mod CoinFlip { + use core::num::traits::zero::Zero; + use starknet::{ContractAddress, get_caller_address, get_contract_address,}; + use starknet::storage::{ + Map, StoragePointerReadAccess, StoragePathEntry, StoragePointerWriteAccess + }; + use pragma_lib::abi::{IRandomnessDispatcher, IRandomnessDispatcherTrait}; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + + #[storage] + struct Storage { + eth_dispatcher: IERC20Dispatcher, + flips: Map, + nonce: u64, + randomness_contract_address: ContractAddress, + } + + #[event] + #[derive(Drop, starknet::Event)] + pub enum Event { + Flipped: Flipped, + Landed: Landed, + } + + #[derive(Drop, starknet::Event)] + pub struct Flipped { + pub flip_id: u64, + pub flipper: ContractAddress, + } + + #[derive(Drop, starknet::Event)] + pub struct Landed { + pub flip_id: u64, + pub flipper: ContractAddress, + pub side: Side + } + + #[derive(Drop, Debug, PartialEq, Serde)] + pub enum Side { + Heads, + Tails, + } + + pub mod Errors { + pub const CALLER_NOT_RANDOMNESS: felt252 = 'Caller not randomness contract'; + pub const INVALID_ADDRESS: felt252 = 'Invalid address'; + pub const INVALID_FLIP_ID: felt252 = 'No flip with the given ID'; + pub const REQUESTOR_NOT_SELF: felt252 = 'Requestor is not self'; + pub const TRANSFER_FAILED: felt252 = 'Transfer failed'; + } + + pub const PUBLISH_DELAY: u64 = 1; // return the random value asap + pub const NUM_OF_WORDS: u64 = 1; // one random value is sufficient + pub const CALLBACK_FEE_LIMIT: u128 = 100_000_000_000_000; // 0.0001 ETH + pub const MAX_CALLBACK_FEE_DEPOSIT: u256 = + 500_000_000_000_000; // CALLBACK_FEE_LIMIT * 5; needs to cover the Premium fee + + #[constructor] + fn constructor( + ref self: ContractState, + randomness_contract_address: ContractAddress, + eth_address: ContractAddress + ) { + assert(randomness_contract_address.is_non_zero(), Errors::INVALID_ADDRESS); + assert(eth_address.is_non_zero(), Errors::INVALID_ADDRESS); + self.randomness_contract_address.write(randomness_contract_address); + self.eth_dispatcher.write(IERC20Dispatcher { contract_address: eth_address }); + } + + #[abi(embed_v0)] + impl CoinFlip of super::ICoinFlip { + /// The contract needs to be funded with some ETH in order for this function + /// to be callable. For simplicity, anyone can fund the contract. + fn flip(ref self: ContractState) { + let flip_id = self._request_my_randomness(); + let flipper = get_caller_address(); + self.flips.entry(flip_id).write(flipper); + self.emit(Event::Flipped(Flipped { flip_id, flipper })); + } + } + + #[abi(embed_v0)] + impl PragmaVRF of super::IPragmaVRF { + fn receive_random_words( + ref self: ContractState, + requestor_address: ContractAddress, + request_id: u64, + random_words: Span, + calldata: Array + ) { + let caller = get_caller_address(); + assert( + caller == self.randomness_contract_address.read(), Errors::CALLER_NOT_RANDOMNESS + ); + + let this = get_contract_address(); + assert(requestor_address == this, Errors::REQUESTOR_NOT_SELF); + + self._process_coin_flip(request_id, random_words.at(0)); + } + } + + #[generate_trait] + impl Private of PrivateTrait { + fn _request_my_randomness(ref self: ContractState) -> u64 { + let randomness_contract_address = self.randomness_contract_address.read(); + let randomness_dispatcher = IRandomnessDispatcher { + contract_address: randomness_contract_address + }; + + let this = get_contract_address(); + + // Approve the randomness contract to transfer the callback deposit/fee + let eth_dispatcher = self.eth_dispatcher.read(); + eth_dispatcher.approve(randomness_contract_address, MAX_CALLBACK_FEE_DEPOSIT); + + let nonce = self.nonce.read(); + + // Request the randomness to be used to construct the winning combination + let request_id = randomness_dispatcher + .request_random( + nonce, this, CALLBACK_FEE_LIMIT, PUBLISH_DELAY, NUM_OF_WORDS, array![] + ); + + self.nonce.write(nonce + 1); + + request_id + } + + fn _process_coin_flip(ref self: ContractState, flip_id: u64, random_value: @felt252) { + let flipper = self.flips.entry(flip_id).read(); + assert(flipper.is_non_zero(), Errors::INVALID_FLIP_ID); + + let random_value: u256 = (*random_value).into(); + let side = if random_value % 2 == 0 { + Side::Heads + } else { + Side::Tails + }; + + self.emit(Event::Landed(Landed { flip_id, flipper, side })); + } + } +} diff --git a/listings/applications/coin_flip/src/lib.cairo b/listings/applications/coin_flip/src/lib.cairo new file mode 100644 index 00000000..a4238d37 --- /dev/null +++ b/listings/applications/coin_flip/src/lib.cairo @@ -0,0 +1,5 @@ +mod contract; +mod mock_randomness; + +#[cfg(test)] +mod tests; diff --git a/listings/applications/coin_flip/src/mock_randomness.cairo b/listings/applications/coin_flip/src/mock_randomness.cairo new file mode 100644 index 00000000..54f2f327 --- /dev/null +++ b/listings/applications/coin_flip/src/mock_randomness.cairo @@ -0,0 +1,166 @@ +#[starknet::contract] +pub mod MockRandomness { + use pragma_lib::abi::IRandomness; + use pragma_lib::types::RequestStatus; + use starknet::{ContractAddress, ClassHash, get_caller_address, get_contract_address}; + use starknet::storage::{ + Map, StoragePointerReadAccess, StoragePathEntry, StoragePointerWriteAccess + }; + use core::num::traits::zero::Zero; + use coin_flip::contract::{IPragmaVRFDispatcher, IPragmaVRFDispatcherTrait}; + use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; + + #[storage] + struct Storage { + eth_dispatcher: IERC20Dispatcher, + next_request_id: u64, + total_fees: Map<(ContractAddress, u64), u256>, + } + + #[event] + #[derive(Drop, starknet::Event)] + enum Event {} + + pub mod Errors { + pub const INVALID_ADDRESS: felt252 = 'Invalid address'; + pub const TRANSFER_FAILED: felt252 = 'Transfer failed'; + } + + #[constructor] + fn constructor(ref self: ContractState, eth_address: ContractAddress) { + assert(eth_address.is_non_zero(), Errors::INVALID_ADDRESS); + self.eth_dispatcher.write(IERC20Dispatcher { contract_address: eth_address }); + } + + #[abi(embed_v0)] + impl MockRandomness of IRandomness { + fn request_random( + ref self: ContractState, + seed: u64, + callback_address: ContractAddress, + callback_fee_limit: u128, + publish_delay: u64, + num_words: u64, + calldata: Array + ) -> u64 { + let caller = get_caller_address(); + let this = get_contract_address(); + + let total_fee: u256 = callback_fee_limit.into() * 5; + let eth_dispatcher = self.eth_dispatcher.read(); + let success = eth_dispatcher.transfer_from(caller, this, total_fee); + assert(success, Errors::TRANSFER_FAILED); + + let request_id = self.next_request_id.read(); + self.next_request_id.write(request_id + 1); + + self.total_fees.entry((caller, request_id)).write(total_fee); + + request_id + } + + fn submit_random( + ref self: ContractState, + request_id: u64, + requestor_address: ContractAddress, + seed: u64, + minimum_block_number: u64, + callback_address: ContractAddress, + callback_fee_limit: u128, + callback_fee: u128, + random_words: Span, + proof: Span, + calldata: Array + ) { + let requestor = IPragmaVRFDispatcher { contract_address: callback_address }; + requestor.receive_random_words(requestor_address, request_id, random_words, calldata); + let eth_dispatcher = self.eth_dispatcher.read(); + let success = eth_dispatcher + .transfer(requestor_address, (callback_fee_limit - callback_fee).into()); + assert(success, Errors::TRANSFER_FAILED); + } + + fn get_total_fees( + self: @ContractState, caller_address: ContractAddress, request_id: u64 + ) -> u256 { + self.total_fees.entry((caller_address, request_id)).read() + } + + + fn compute_premium_fee(self: @ContractState, caller_address: ContractAddress) -> u128 { + panic!("unimplemented 'compute_premium_fee'") + } + fn update_status( + ref self: ContractState, + requestor_address: ContractAddress, + request_id: u64, + new_status: RequestStatus + ) { + panic!("unimplemented") + } + + fn cancel_random_request( + ref self: ContractState, + request_id: u64, + requestor_address: ContractAddress, + seed: u64, + minimum_block_number: u64, + callback_address: ContractAddress, + callback_fee_limit: u128, + num_words: u64 + ) { + panic!("unimplemented") + } + + fn get_pending_requests( + self: @ContractState, requestor_address: ContractAddress, offset: u64, max_len: u64 + ) -> Span { + panic!("unimplemented") + } + + fn get_request_status( + self: @ContractState, requestor_address: ContractAddress, request_id: u64 + ) -> RequestStatus { + panic!("unimplemented") + } + fn requestor_current_index( + self: @ContractState, requestor_address: ContractAddress + ) -> u64 { + panic!("unimplemented") + } + fn get_public_key(self: @ContractState, requestor_address: ContractAddress) -> felt252 { + panic!("unimplemented") + } + fn get_payment_token(self: @ContractState) -> ContractAddress { + panic!("unimplemented") + } + fn set_payment_token(ref self: ContractState, token_contract: ContractAddress) { + panic!("unimplemented") + } + fn upgrade(ref self: ContractState, impl_hash: ClassHash) { + panic!("unimplemented") + } + fn refund_operation( + ref self: ContractState, caller_address: ContractAddress, request_id: u64 + ) { + panic!("unimplemented") + } + fn get_out_of_gas_requests( + self: @ContractState, requestor_address: ContractAddress, + ) -> Span { + panic!("unimplemented") + } + fn withdraw_funds(ref self: ContractState, receiver_address: ContractAddress) { + panic!("unimplemented") + } + fn get_contract_balance(self: @ContractState) -> u256 { + panic!("unimplemented") + } + fn get_admin_address(self: @ContractState,) -> ContractAddress { + panic!("unimplemented") + } + fn set_admin_address(ref self: ContractState, new_admin_address: ContractAddress) { + panic!("unimplemented") + } + } +} diff --git a/listings/applications/coin_flip/src/tests.cairo b/listings/applications/coin_flip/src/tests.cairo new file mode 100644 index 00000000..bdaa0466 --- /dev/null +++ b/listings/applications/coin_flip/src/tests.cairo @@ -0,0 +1,287 @@ +use coin_flip::contract::{ + CoinFlip, CoinFlip::{Side, CALLBACK_FEE_LIMIT}, ICoinFlipDispatcher, ICoinFlipDispatcherTrait, +}; +use starknet::{ContractAddress, contract_address_const}; +use snforge_std::{ + declare, start_cheat_caller_address, stop_cheat_caller_address, spy_events, + EventSpyAssertionsTrait, DeclareResultTrait, ContractClassTrait +}; +use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait}; +use pragma_lib::abi::{IRandomnessDispatcher, IRandomnessDispatcherTrait}; + +fn deploy() -> (ICoinFlipDispatcher, IRandomnessDispatcher, IERC20Dispatcher, ContractAddress) { + // deploy mock ETH token + let eth_contract = declare("ERC20Upgradeable").unwrap().contract_class(); + let eth_name: ByteArray = "Ethereum"; + let eth_symbol: ByteArray = "ETH"; + let eth_supply: u256 = CALLBACK_FEE_LIMIT.into() * 100; + let mut eth_ctor_calldata = array![]; + let deployer = contract_address_const::<'deployer'>(); + ((eth_name, eth_symbol, eth_supply, deployer), deployer).serialize(ref eth_ctor_calldata); + let eth_address = eth_contract.precalculate_address(@eth_ctor_calldata); + start_cheat_caller_address(eth_address, deployer); + eth_contract.deploy(@eth_ctor_calldata).unwrap(); + stop_cheat_caller_address(eth_address); + + // deploy MockRandomness + let mock_randomness = declare("MockRandomness").unwrap().contract_class(); + let mut randomness_calldata: Array = array![]; + (eth_address).serialize(ref randomness_calldata); + let (randomness_address, _) = mock_randomness.deploy(@randomness_calldata).unwrap(); + + // deploy the actual CoinFlip contract + let coin_flip_contract = declare("CoinFlip").unwrap().contract_class(); + let mut coin_flip_ctor_calldata: Array = array![]; + (randomness_address, eth_address).serialize(ref coin_flip_ctor_calldata); + let (coin_flip_address, _) = coin_flip_contract.deploy(@coin_flip_ctor_calldata).unwrap(); + + let eth_dispatcher = IERC20Dispatcher { contract_address: eth_address }; + let randomness_dispatcher = IRandomnessDispatcher { contract_address: randomness_address }; + let coin_flip_dispatcher = ICoinFlipDispatcher { contract_address: coin_flip_address }; + + (coin_flip_dispatcher, randomness_dispatcher, eth_dispatcher, deployer) +} + +#[test] +fn test_all_relevant_random_words() { + let (coin_flip, randomness, eth, deployer) = deploy(); + + // fund the CoinFlip contract + start_cheat_caller_address(eth.contract_address, deployer); + eth.transfer(coin_flip.contract_address, CALLBACK_FEE_LIMIT.into() * 100); + stop_cheat_caller_address(eth.contract_address); + + let random_words: Array<(felt252, Side, u64)> = array![ + (0, Side::Heads, 0), + (2, Side::Heads, 1), + (4, Side::Heads, 2), + (1000, Side::Heads, 3), + (12345654320, Side::Heads, 4), + (1, Side::Tails, 5), + (3, Side::Tails, 6), + (5, Side::Tails, 7), + (1001, Side::Tails, 8), + (12345654321, Side::Tails, 9), + ]; + for ( + random_word, expected_side, expected_request_id + ) in random_words { + _flip_request( + coin_flip, + randomness, + eth, + deployer, + expected_request_id, + CALLBACK_FEE_LIMIT / 5 * 3, + random_word, + expected_side + ); + } +} + +#[test] +fn test_multiple_flips() { + let (coin_flip, randomness, eth, deployer) = deploy(); + + // fund the CoinFlip contract + start_cheat_caller_address(eth.contract_address, deployer); + eth.transfer(coin_flip.contract_address, CALLBACK_FEE_LIMIT.into() * 50); + stop_cheat_caller_address(eth.contract_address); + + _flip_request( + coin_flip, randomness, eth, deployer, 0, CALLBACK_FEE_LIMIT / 5 * 3, 123456789, Side::Tails + ); + _flip_request( + coin_flip, + randomness, + eth, + deployer, + 1, + CALLBACK_FEE_LIMIT / 4 * 3, + 12345654321, + Side::Tails + ); + _flip_request(coin_flip, randomness, eth, deployer, 2, CALLBACK_FEE_LIMIT, 3, Side::Tails); +} + +fn _flip_request( + coin_flip: ICoinFlipDispatcher, + randomness: IRandomnessDispatcher, + eth: IERC20Dispatcher, + deployer: ContractAddress, + expected_request_id: u64, + expected_callback_fee: u128, + random_word: felt252, + expected_side: Side +) { + let original_balance = eth.balance_of(coin_flip.contract_address); + + let mut spy = spy_events(); + + start_cheat_caller_address(coin_flip.contract_address, deployer); + coin_flip.flip(); + stop_cheat_caller_address(coin_flip.contract_address); + + spy + .assert_emitted( + @array![ + ( + coin_flip.contract_address, + CoinFlip::Event::Flipped( + CoinFlip::Flipped { flip_id: expected_request_id, flipper: deployer } + ) + ) + ] + ); + + let post_flip_balance = eth.balance_of(coin_flip.contract_address); + assert_eq!( + post_flip_balance, + original_balance + - randomness.get_total_fees(coin_flip.contract_address, expected_request_id) + ); + + randomness + .submit_random( + expected_request_id, + coin_flip.contract_address, + 0, + 0, + coin_flip.contract_address, + CALLBACK_FEE_LIMIT, + expected_callback_fee, + array![random_word].span(), + array![].span(), + array![] + ); + + spy + .assert_emitted( + @array![ + ( + coin_flip.contract_address, + CoinFlip::Event::Landed( + CoinFlip::Landed { + flip_id: expected_request_id, flipper: deployer, side: expected_side + } + ) + ) + ] + ); + + assert_eq!( + eth.balance_of(coin_flip.contract_address), + post_flip_balance + (CALLBACK_FEE_LIMIT - expected_callback_fee).into() + ); +} + +#[test] +fn test_two_consecutive_flips() { + let (coin_flip, randomness, eth, deployer) = deploy(); + + // fund the CoinFlip contract + start_cheat_caller_address(eth.contract_address, deployer); + eth.transfer(coin_flip.contract_address, CALLBACK_FEE_LIMIT.into() * 50); + stop_cheat_caller_address(eth.contract_address); + + let mut spy = spy_events(); + + let original_balance = eth.balance_of(coin_flip.contract_address); + + let other_flipper = contract_address_const::<'other_flipper'>(); + + start_cheat_caller_address(coin_flip.contract_address, deployer); + coin_flip.flip(); + start_cheat_caller_address(coin_flip.contract_address, other_flipper); + coin_flip.flip(); + stop_cheat_caller_address(coin_flip.contract_address); + + spy + .assert_emitted( + @array![ + ( + coin_flip.contract_address, + CoinFlip::Event::Flipped(CoinFlip::Flipped { flip_id: 0, flipper: deployer }) + ), + ( + coin_flip.contract_address, + CoinFlip::Event::Flipped( + CoinFlip::Flipped { flip_id: 1, flipper: other_flipper } + ) + ) + ] + ); + + let post_flip_balance = eth.balance_of(coin_flip.contract_address); + let first_flip_fee = randomness.get_total_fees(coin_flip.contract_address, 0); + let second_flip_fee = randomness.get_total_fees(coin_flip.contract_address, 1); + assert_eq!(post_flip_balance, original_balance - first_flip_fee - second_flip_fee); + + let expected_callback_fee = CALLBACK_FEE_LIMIT / 5 * 3; + let random_word_deployer = 5633; + let expected_side_deployer = Side::Tails; + let random_word_other_flipper = 8000; + let expected_side_other_flipper = Side::Heads; + + randomness + .submit_random( + 0, + coin_flip.contract_address, + 0, + 0, + coin_flip.contract_address, + CALLBACK_FEE_LIMIT, + expected_callback_fee, + array![random_word_deployer].span(), + array![].span(), + array![] + ); + randomness + .submit_random( + 1, + coin_flip.contract_address, + 0, + 0, + coin_flip.contract_address, + CALLBACK_FEE_LIMIT, + expected_callback_fee, + array![random_word_other_flipper].span(), + array![].span(), + array![] + ); + + spy + .assert_emitted( + @array![ + ( + coin_flip.contract_address, + CoinFlip::Event::Landed( + CoinFlip::Landed { + flip_id: 0, flipper: deployer, side: expected_side_deployer + } + ) + ), + ( + coin_flip.contract_address, + CoinFlip::Event::Landed( + CoinFlip::Landed { + flip_id: 1, flipper: other_flipper, side: expected_side_other_flipper + } + ) + ) + ] + ); + + assert_eq!( + eth.balance_of(coin_flip.contract_address), + post_flip_balance + (CALLBACK_FEE_LIMIT - expected_callback_fee).into() * 2 + ); +} + +#[test] +#[should_panic(expected: 'ERC20: insufficient balance')] +fn test_flip_without_enough_for_fees() { + let (coin_flip, _, _, deployer) = deploy(); + start_cheat_caller_address(coin_flip.contract_address, deployer); + coin_flip.flip(); +} diff --git a/listings/applications/crowdfunding/src/tests.cairo b/listings/applications/crowdfunding/src/tests.cairo index ba01cf83..11f2aaa5 100644 --- a/listings/applications/crowdfunding/src/tests.cairo +++ b/listings/applications/crowdfunding/src/tests.cairo @@ -1,4 +1,3 @@ -use core::result::ResultTrait; use starknet::{ContractAddress, get_block_timestamp, contract_address_const,}; use snforge_std::{ declare, ContractClass, ContractClassTrait, start_cheat_caller_address, diff --git a/src/SUMMARY.md b/src/SUMMARY.md index a74bd1cd..54dd0313 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -64,6 +64,7 @@ Summary - [Simple Storage with Starknet-js](./applications/simple_storage_starknetjs.md) - [Crowdfunding Campaign](./applications/crowdfunding.md) - [AdvancedFactory: Crowdfunding](./applications/advanced_factory.md) +- [Random Number Generator](./applications/random_number_generator.md) diff --git a/src/applications/random_number_generator.md b/src/applications/random_number_generator.md new file mode 100644 index 00000000..8b7c5990 --- /dev/null +++ b/src/applications/random_number_generator.md @@ -0,0 +1,64 @@ +# Random Number Generator + +Randomness plays a crucial role in blockchain and smart contract development. In the context of blockchain, randomness is about generating unpredictable values using some source of entropy that is fair and resistant to manipulation. + +In blockchain and smart contracts, randomness is needed for: + +- **Gaming:** Ensuring fair outcomes in games of chance. +- **Lotteries:** Selecting winners in a verifiable and unbiased manner. +- **Security:** Generating cryptographic keys and nonces that are hard to predict. +- **Consensus Protocols:** Selecting validators or block producers in some proof-of-stake systems. + +However, achieving true randomness on a decentralized platform poses significant challenges. There are numerous sources of entropy, each with its strengths and weaknesses. + +### Sources of Entropy + +#### 1. Block Properties + +- **Description:** Using properties of the blockchain itself, like the hash of a block, or a block timestamp, as a source of randomness. +- **Example:** A common approach is to use the hash of a recent block as a seed for random number generation. +- **Risks:** + - **Predictability:** Miners can influence future block hashes by controlling the nonce they use during mining. + - **Manipulation:** Many of the blockchain properties (block hash, timestamp etc.) can be manipulated by some entities, especially if they stand to gain from a specific random outcome. + +#### 2. User-Provided Inputs + +- **Description:** Allowing users to provide entropy directly, often combined with other sources to generate a random number. +- **Example:** Users submitting their own random values which are then hashed together with other inputs. +- **Risks:** + - **Collusion:** Users may collude to provide inputs that skew the randomness in their favor. + - **Front-Running:** Other participants might observe a user's input and act on it before it gets included in the block, affecting the outcome. + +#### 3. External Oracles + +- **Description:** Using a trusted third-party service to supply randomness. Oracles are off-chain services that provide data to smart contracts. +- **Example:** Pragma VRF (Verifiable Random Function) is a service that provides cryptographically secure randomness. +- **Risks:** + - **Trust:** Reliance on a third party undermines the trustless nature of blockchain. + - **Centralization:** If the oracle service is compromised or shut down, so is the randomness it provides. + - **Cost:** Using an oracle often involves additional transaction fees. + +#### 4. Commit-Reveal Schemes + +- **Description:** A multi-phase protocol where participants commit to a value in the first phase and reveal it in the second. +- **Example:** Participants submit a hash of their random value (commitment) first and reveal the actual value later. The final random number is derived from all revealed values. +- **Risks:** + - **Dishonest Behavior:** Participants may choose not to reveal their values if the outcome is unfavorable. + - **Coordination:** Requires honest participation from multiple parties, which can be hard to guarantee. + + +> There are other ways to generate randomness on-chain, for more information read the ["Public Randomness and Randomness Beacons"](https://a16zcrypto.com/posts/article/public-randomness-and-randomness-beacons/) article. + +## CoinFlip using Pragma VRF + +Below is an implementation of a `CoinFlip` contract that utilizes a [Pragma Verifiable Random Function (VRF)](https://docs.pragma.build/Resources/Cairo%201/randomness/randomness) to generate random numbers on-chain. + +- Players can flip a virtual coin and receive a random outcome of `Heads` or `Tails` +- The contract needs to be funded with enough ETH to perform the necessary operations, including paying fees to Pragma's Randomness Oracle which returns a random value +- When the coin is "flipped", the contract makes a call to the Randomness Oracle to request a random value and the `Flipped` event is emitted +- Randomness is generated off-chain, and then submitted to the contract using the `receive_random_words` callback +- Based on this random value, the contract determines whether the coin "landed" on `Heads` or on `Tails`, and the `Landed` event is emitted + +```rust +{{#include ../../listings/applications/coin_flip/src/contract.cairo}} +``` From 369849982815e4b5058d42491bd22105e81407f2 Mon Sep 17 00:00:00 2001 From: hudem1 <55464342+hudem1@users.noreply.github.com> Date: Tue, 1 Oct 2024 16:49:12 +0900 Subject: [PATCH 32/36] feat(merkle-tree): Contract with tests (#228) * feat(merkle-tree): Contract with tests * feat(merkle-tree): Corrections according to PR reviews * feat(merkle-tree): Contract with tests * fix: 2024_07 edition * fix: Replace Map simulating Array with Vec - streamline md file explanations * fix: scarb fmt --------- Co-authored-by: julio4 <30329843+julio4@users.noreply.github.com> --- Scarb.lock | 4 + listings/applications/merkle_tree/.gitignore | 1 + listings/applications/merkle_tree/Scarb.toml | 15 ++ .../merkle_tree/src/contract.cairo | 117 +++++++++++ .../applications/merkle_tree/src/lib.cairo | 4 + .../applications/merkle_tree/src/tests.cairo | 182 ++++++++++++++++++ src/SUMMARY.md | 1 + src/applications/merkle_tree.md | 66 +++++++ src/assets/merkle_root.png | Bin 0 -> 190423 bytes 9 files changed, 390 insertions(+) create mode 100644 listings/applications/merkle_tree/.gitignore create mode 100644 listings/applications/merkle_tree/Scarb.toml create mode 100644 listings/applications/merkle_tree/src/contract.cairo create mode 100644 listings/applications/merkle_tree/src/lib.cairo create mode 100644 listings/applications/merkle_tree/src/tests.cairo create mode 100644 src/applications/merkle_tree.md create mode 100644 src/assets/merkle_root.png diff --git a/Scarb.lock b/Scarb.lock index 2aaddaf7..b73c3911 100644 --- a/Scarb.lock +++ b/Scarb.lock @@ -110,6 +110,10 @@ version = "0.1.0" name = "mappings" version = "0.1.0" +[[package]] +name = "merkle_tree" +version = "0.1.0" + [[package]] name = "nft_dutch_auction" version = "0.1.0" diff --git a/listings/applications/merkle_tree/.gitignore b/listings/applications/merkle_tree/.gitignore new file mode 100644 index 00000000..eb5a316c --- /dev/null +++ b/listings/applications/merkle_tree/.gitignore @@ -0,0 +1 @@ +target diff --git a/listings/applications/merkle_tree/Scarb.toml b/listings/applications/merkle_tree/Scarb.toml new file mode 100644 index 00000000..bba5cb58 --- /dev/null +++ b/listings/applications/merkle_tree/Scarb.toml @@ -0,0 +1,15 @@ +[package] +name = "merkle_tree" +version.workspace = true +edition = "2024_07" + +[dependencies] +starknet.workspace = true + +[dev-dependencies] +cairo_test.workspace = true + +[scripts] +test.workspace = true + +[[target.starknet-contract]] diff --git a/listings/applications/merkle_tree/src/contract.cairo b/listings/applications/merkle_tree/src/contract.cairo new file mode 100644 index 00000000..bc5a2d67 --- /dev/null +++ b/listings/applications/merkle_tree/src/contract.cairo @@ -0,0 +1,117 @@ +#[generate_trait] +pub impl ByteArrayHashTraitImpl of ByteArrayHashTrait { + fn hash(self: @ByteArray) -> felt252 { + let mut serialized_byte_arr: Array = ArrayTrait::new(); + self.serialize(ref serialized_byte_arr); + + core::poseidon::poseidon_hash_span(serialized_byte_arr.span()) + } +} + +#[starknet::interface] +pub trait IMerkleTree { + fn build_tree(ref self: TContractState, data: Array) -> Array; + fn get_root(self: @TContractState) -> felt252; + // function to verify if leaf node exists in the merkle tree + fn verify( + self: @TContractState, proof: Array, root: felt252, leaf: felt252, index: usize + ) -> bool; +} + +mod errors { + pub const NOT_POW_2: felt252 = 'Data length is not a power of 2'; + pub const NOT_PRESENT: felt252 = 'No element in merkle tree'; +} + +#[starknet::contract] +pub mod MerkleTree { + use core::poseidon::PoseidonTrait; + use core::hash::{HashStateTrait, HashStateExTrait}; + use starknet::storage::{ + StoragePointerWriteAccess, StoragePointerReadAccess, Vec, MutableVecTrait, VecTrait + }; + use super::ByteArrayHashTrait; + + #[storage] + struct Storage { + pub hashes: Vec + } + + #[derive(Drop, Serde, Copy)] + struct Vec2 { + x: u32, + y: u32 + } + + #[abi(embed_v0)] + impl IMerkleTreeImpl of super::IMerkleTree { + fn build_tree(ref self: ContractState, mut data: Array) -> Array { + let data_len = data.len(); + assert(data_len > 0 && (data_len & (data_len - 1)) == 0, super::errors::NOT_POW_2); + + let mut _hashes: Array = ArrayTrait::new(); + + // first, hash every leaf + for value in data { + _hashes.append(value.hash()); + }; + + // then, hash all levels above leaves + let mut current_nodes_lvl_len = data_len; + let mut hashes_offset = 0; + + while current_nodes_lvl_len > 0 { + let mut i = 0; + while i < current_nodes_lvl_len - 1 { + let left_elem = *_hashes.at(hashes_offset + i); + let right_elem = *_hashes.at(hashes_offset + i + 1); + + let hash = PoseidonTrait::new().update_with((left_elem, right_elem)).finalize(); + _hashes.append(hash); + + i += 2; + }; + + hashes_offset += current_nodes_lvl_len; + current_nodes_lvl_len /= 2; + }; + + // write to the contract state (useful for the get_root function) + for hash in _hashes.span() { + self.hashes.append().write(*hash); + }; + + _hashes + } + + fn get_root(self: @ContractState) -> felt252 { + let merkle_tree_length = self.hashes.len(); + assert(merkle_tree_length > 0, super::errors::NOT_PRESENT); + + self.hashes.at(merkle_tree_length - 1).read() + } + + fn verify( + self: @ContractState, + mut proof: Array, + root: felt252, + leaf: felt252, + mut index: usize + ) -> bool { + let mut current_hash = leaf; + + while let Option::Some(value) = proof.pop_front() { + current_hash = + if index % 2 == 0 { + PoseidonTrait::new().update_with((current_hash, value)).finalize() + } else { + PoseidonTrait::new().update_with((value, current_hash)).finalize() + }; + + index /= 2; + }; + + current_hash == root + } + } +} diff --git a/listings/applications/merkle_tree/src/lib.cairo b/listings/applications/merkle_tree/src/lib.cairo new file mode 100644 index 00000000..11ada17a --- /dev/null +++ b/listings/applications/merkle_tree/src/lib.cairo @@ -0,0 +1,4 @@ +mod contract; + +#[cfg(test)] +mod tests; diff --git a/listings/applications/merkle_tree/src/tests.cairo b/listings/applications/merkle_tree/src/tests.cairo new file mode 100644 index 00000000..9188cea3 --- /dev/null +++ b/listings/applications/merkle_tree/src/tests.cairo @@ -0,0 +1,182 @@ +use merkle_tree::contract::IMerkleTreeDispatcherTrait; +use merkle_tree::contract::{IMerkleTreeDispatcher, MerkleTree, ByteArrayHashTrait}; +use starknet::syscalls::deploy_syscall; +use starknet::{ContractAddress, SyscallResultTrait}; +use starknet::testing::set_contract_address; +use core::poseidon::PoseidonTrait; +use core::hash::{HashStateTrait, HashStateExTrait}; +use starknet::storage::{VecTrait, StoragePointerReadAccess}; + +fn deploy_util(class_hash: felt252, calldata: Array) -> ContractAddress { + let (address, _) = deploy_syscall(class_hash.try_into().unwrap(), 0, calldata.span(), false) + .unwrap_syscall(); + address +} + +fn setup() -> IMerkleTreeDispatcher { + let contract_address = deploy_util(MerkleTree::TEST_CLASS_HASH, array![]); + + IMerkleTreeDispatcher { contract_address } +} + +#[test] +fn should_deploy() { + let deploy = setup(); + + let state = @MerkleTree::contract_state_for_testing(); + // "link" a new MerkleTree struct to the deployed MerkleTree contract + // in order to access its internal state fields for assertions + set_contract_address(deploy.contract_address); + + assert_eq!(state.hashes.len(), 0); +} + +#[test] +fn build_tree_succeeds() { + /// Set up + let deploy = setup(); + + let data_1 = "alice -> bob: 2"; + let data_2 = "bob -> john: 5"; + let data_3 = "alice -> john: 1"; + let data_4 = "john -> alex: 8"; + let arguments = array![data_1.clone(), data_2.clone(), data_3.clone(), data_4.clone()]; + + /// When + let actual_hashes = deploy.build_tree(arguments); + + /// Then + let mut expected_hashes: Array = array![]; + + // leaves' hashes + expected_hashes.append(data_1.hash()); + expected_hashes.append(data_2.hash()); + expected_hashes.append(data_3.hash()); + expected_hashes.append(data_4.hash()); + + // hashes for level above leaves + let hash_0 = PoseidonTrait::new() + .update_with((*expected_hashes.at(0), *expected_hashes.at(1))) + .finalize(); + let hash_1 = PoseidonTrait::new() + .update_with((*expected_hashes.at(2), *expected_hashes.at(3))) + .finalize(); + expected_hashes.append(hash_0); + expected_hashes.append(hash_1); + + // root hash + let root_hash = PoseidonTrait::new().update_with((hash_0, hash_1)).finalize(); + expected_hashes.append(root_hash); + + // verify returned result + assert_eq!(actual_hashes, expected_hashes); + + // verify get_root + assert_eq!(deploy.get_root(), root_hash); + + // verify contract storage state + + let state = @MerkleTree::contract_state_for_testing(); + // "link" a new MerkleTree struct to the deployed MerkleTree contract + // in order to access its internal state fields for assertions + set_contract_address(deploy.contract_address); + + assert_eq!(state.hashes.len(), expected_hashes.len().into()); + + for i in 0 + ..expected_hashes + .len() { + assert_eq!(state.hashes.at(i.into()).read(), *expected_hashes.at(i)); + } +} + +#[test] +#[should_panic(expected: ('Data length is not a power of 2', 'ENTRYPOINT_FAILED'))] +fn build_tree_fails() { + /// Set up + let deploy = setup(); + + let data_1 = "alice -> bob: 2"; + let data_2 = "bob -> john: 5"; + let data_3 = "alice -> john: 1"; + // number of arguments not a power of 2 + let arguments = array![data_1, data_2, data_3]; + + /// When + deploy.build_tree(arguments); +} + +#[test] +fn verify_leaf_succeeds() { + /// Set up + let deploy = setup(); + + let data_1 = "bob -> alice: 1"; + let data_2 = "alex -> john: 3"; + let data_3 = "alice -> alex: 8"; + let data_4 = "alex -> bob: 8"; + let arguments = array![data_1.clone(), data_2.clone(), data_3.clone(), data_4.clone()]; + + let hashes = deploy.build_tree(arguments); + + // ----> hashes tree : + // + // hashes[6] + // / \ + // hashes[4] hashes[5] + // / \ / \ + // hashes[0] hashes[1] hashes[2] hashes[3] + + let res = deploy + .verify( + array![*hashes.at(3), *hashes.at(4)], // proof + *hashes.at(6), // root + data_3.hash(), // leaf + 2 // leaf index + ); + + assert(res, 'Leaf should be in merkle tree'); +} + +#[test] +#[available_gas(20000000)] +fn verify_leaf_fails() { + /// Set up + let deploy = setup(); + + let data_1 = "bob -> alice: 1"; + let data_2 = "alex -> john: 3"; + let data_3 = "alice -> alex: 8"; + let data_4 = "alex -> bob: 8"; + let arguments = array![data_1.clone(), data_2.clone(), data_3.clone(), data_4.clone()]; + + let hashes = deploy.build_tree(arguments); + + // ----- hashes tree ----- + // hashes[6] + // / \ + // hashes[4] hashes[5] + // / \ / \ + // hashes[0] hashes[1] hashes[2] hashes[3] + + let wrong_leaf: ByteArray = "alice -> alex: 9"; + let res = deploy + .verify( + array![*hashes.at(3), *hashes.at(4)], // proof + *hashes.at(6), // root + wrong_leaf.hash(), // leaf + 2 // leaf index + ); + assert(!res, '1- Leaf should NOT be in tree'); + + let wrong_proof = array![*hashes.at(4), *hashes.at(3)]; + let res = deploy + .verify( + wrong_proof, // proof + *hashes.at(6), // root + data_3.hash(), // leaf + 2 // leaf index + ); + assert(!res, '2- Leaf should NOT be in tree'); +} + diff --git a/src/SUMMARY.md b/src/SUMMARY.md index 54dd0313..60b89e4a 100644 --- a/src/SUMMARY.md +++ b/src/SUMMARY.md @@ -61,6 +61,7 @@ Summary - [Constant Product AMM](./applications/constant-product-amm.md) - [TimeLock](./applications/timelock.md) - [Staking](./applications/staking.md) +- [Merkle Tree](./applications/merkle_tree.md) - [Simple Storage with Starknet-js](./applications/simple_storage_starknetjs.md) - [Crowdfunding Campaign](./applications/crowdfunding.md) - [AdvancedFactory: Crowdfunding](./applications/advanced_factory.md) diff --git a/src/applications/merkle_tree.md b/src/applications/merkle_tree.md new file mode 100644 index 00000000..e1d30bd3 --- /dev/null +++ b/src/applications/merkle_tree.md @@ -0,0 +1,66 @@ +# Merkle Tree contract + +A Merkle tree, also known as a hash tree, is a data structure used in cryptography and computer science to verify data integrity and consistency. It is a binary tree where each leaf node represents the cryptographic hash of some data (a transaction for example), and each non-leaf node represents the cryptographic hash of its child nodes. This hierarchical structure allows efficient and secure verification of the data integrity. + +Here's a quick summary of how it operates and what functionalities it supports: + +### How it works: + +1. Leaves Creation: + - Some data is hashed to create a leaf node. +2. Intermediate Nodes Creation: + - Pairwise hashes of the leaf nodes are combined and hashed again to create parent nodes. + - This process continues until only one hash remains, known as the Merkle root. +3. Merkle Root: + - The final hash at the top of the tree, representing the entire dataset. + - Changing any single data block will change its corresponding leaf node, which will propagate up the tree, altering the Merkle root. + +### Key Features: + +1. Efficient Verification: + - Only a small subset of the tree (the Merkle proof) is needed to verify the inclusion of a particular data block, reducing the amount of data that must be processed. + +2. Data Integrity: + - The Merkle root ensures the integrity of all the underlying data blocks. + - Any alteration in the data will result in a different root hash. + +### Examples of use cases: + +1. Fundamental use case: Ethereum blockchain integrity + - Cryptocurrencies like Ethereum use Merkle trees to efficiently verify and maintain transaction integrity within blocks. + - Each transaction in a block is hashed to form leaf nodes, and these hashes are recursively combined to form a single Merkle root, summarizing all transactions. + - The Merkle root is stored in the block header, which is hashed to generate the block's unique identifier. + - Guaranteed Integrity: Any change to a transaction alters the Merkle root, block header, and block hash, making it easy for nodes to detect tampering. + - Transaction verification: Nodes can verify specific transactions via Merkle proofs without downloading the entire block. + +2. Whitelist inclusion + - Merkle trees allow efficient whitelist verification without storing the full list on-chain, reducing storage costs. + - The Merkle root of the whitelist is stored on-chain, while the full list remains off-chain. + - To verify if an address is on the whitelist, a user provides a Merkle proof and the address. The Merkle root is recalculated using the provided data and compared to the stored on-chain root. If they match, the address is included; if not, it's excluded. + +3. Decentralized Identity Verification + - Merkle trees can be used in decentralized identity systems to verify credentials. + - Off-chain data: a user's credentials. + - On-chain data: the Merkle root representing the credentials. + +### Visual example + +![Diagram of the Merkle Tree](../assets/merkle_root.png) + +The above diagram represents a merkle tree.\ +Each leaf node is the hash of some data.\ +Each other node is the hash of the combination of both children nodes. + +If we were to `verify` the `hash 6`, the merkle proof would need to contain the `hash 5`, `hash 12`and `hash 13`: + 1. The `hash 5` would be combined with the `hash 6` to re-compute the `hash 11`. + 2. The newly computed `hash 11` in step 1 would be combined with `hash 12` to re-compute `hash 14`. + 3. The `hash 13` would be combined with the newly computed `hash 14` in step 2 to re-compute the merkle root. + 4. We can then compare the computed resultant merkle root with the one provided to the `verify` function. + +### Code + +The following implementation is the Cairo adaptation of the [Solidity by Example - Merkle Tree contract](https://solidity-by-example.org/app/merkle-tree/). + +```rust +{{#include ../../listings/applications/merkle_tree/src/contract.cairo}} +``` diff --git a/src/assets/merkle_root.png b/src/assets/merkle_root.png new file mode 100644 index 0000000000000000000000000000000000000000..2c97ce07e64a60f6a60e91e374cd8faaec7f76d4 GIT binary patch literal 190423 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 literal 0 HcmV?d00001 From 70be4b9d20ec646bfc88089d52059b93390eb95a Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Thu, 3 Oct 2024 16:53:01 +0800 Subject: [PATCH 33/36] Improvement: typos check in CI/CD (#248) * fix: typos * ci/cd: typos check in ci * fix: cases * fix: randomness requestor typos --- .github/pull_request_template.md | 5 +- .github/workflows/mdbook.yml | 5 +- .github/workflows/verify_cairo_programs.yml | 6 +++ CONTRIBUTING.md | 7 +++ _typos.toml | 9 ++++ .../advanced_factory/src/contract.cairo | 4 +- .../advanced_factory/src/tests.cairo | 2 +- .../crowdfunding/src/campaign.cairo | 2 +- listings/applications/erc20/src/token.cairo | 48 +++++++++---------- .../upgradeable_contract/src/tests.cairo | 2 +- .../factory/src/simple_factory.cairo | 2 +- .../visibility/src/visibility.cairo | 2 +- po/{messages.pot => messages.po} | 0 src/applications/crowdfunding.md | 2 +- src/getting-started/basics/syscalls.md | 2 +- src/getting-started/cairo_cheatsheet/enums.md | 2 +- src/getting-started/interacting/factory.md | 2 +- .../interacting/how_to_deploy.md | 2 +- 18 files changed, 61 insertions(+), 43 deletions(-) create mode 100644 _typos.toml rename po/{messages.pot => messages.po} (100%) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 6896e30b..f0330a67 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -1,4 +1,4 @@ -**Issue(s):** +**Issue(s): Close #issue-number ### Description @@ -8,5 +8,4 @@ Please provide a brief description of the changes made in this pull request and - [ ] **CI Verifier:** Run `./scripts/cairo_programs_verifier.sh` successfully - [ ] **Contract Tests:** Added tests to cover the changes - + --> diff --git a/.github/workflows/mdbook.yml b/.github/workflows/mdbook.yml index 6eaf3bfc..e601650b 100644 --- a/.github/workflows/mdbook.yml +++ b/.github/workflows/mdbook.yml @@ -1,7 +1,4 @@ -# Sample workflow for building and deploying a mdBook site to GitHub Pages -# -# To get started with mdBook see: https://rust-lang.github.io/mdBook/index.html -# +# https://rust-lang.github.io/mdBook/index.html name: Deploy mdBook site to Pages on: diff --git a/.github/workflows/verify_cairo_programs.yml b/.github/workflows/verify_cairo_programs.yml index 81a3a802..518a91f8 100644 --- a/.github/workflows/verify_cairo_programs.yml +++ b/.github/workflows/verify_cairo_programs.yml @@ -31,3 +31,9 @@ jobs: run: | chmod +x scripts/cairo_programs_verifier.sh ./scripts/cairo_programs_verifier.sh + + typos: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: crate-ci/typos@master diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 49365e54..d15852c3 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -55,6 +55,13 @@ To add a new chapter, create a new markdown file in the `src` directory. All the Do not write directly Cairo program inside the markdown files. Instead, use code blocks that import the Cairo programs from the `listing` directory. These programs are bundled into scarb projects, which makes it easier to test and build all programs. See the next section for more details. +Be sure to check for typos with `typos`: + +```shell +cargo install typos-cli +typos src/ +``` + ## Adding a new Cairo program You can add or modify examples in the `listings` directory. Each listing is a scarb project. diff --git a/_typos.toml b/_typos.toml new file mode 100644 index 00000000..75bef735 --- /dev/null +++ b/_typos.toml @@ -0,0 +1,9 @@ +[default] +extend-ignore-identifiers-re = ["requestor", "REQUESTOR", "Requestor"] + +[type.po] +extend-glob = ["*.po", "*.css", "*.js"] +check-file = false + +[files] +extend-exclude = ["po/*.po", "listings/**/*.json"] diff --git a/listings/applications/advanced_factory/src/contract.cairo b/listings/applications/advanced_factory/src/contract.cairo index 4c6b4c89..098dda87 100644 --- a/listings/applications/advanced_factory/src/contract.cairo +++ b/listings/applications/advanced_factory/src/contract.cairo @@ -42,7 +42,7 @@ pub mod CampaignFactory { struct Storage { #[substorage(v0)] ownable: ownable_component::Storage, - /// Store all of the created campaign instances' addresses and thei class hashes + /// Store all of the created campaign instances' addresses and their class hashes campaigns: Map<(ContractAddress, ContractAddress), ClassHash>, /// Store the class hash of the contract to deploy campaign_class_hash: ClassHash, @@ -102,7 +102,7 @@ pub mod CampaignFactory { ) -> ContractAddress { let creator = get_caller_address(); - // Create contructor arguments + // Create constructor arguments let mut constructor_calldata: Array:: = array![]; ((creator, title, description, goal), start_time, end_time, token_address) .serialize(ref constructor_calldata); diff --git a/listings/applications/advanced_factory/src/tests.cairo b/listings/applications/advanced_factory/src/tests.cairo index 41588040..4b2b3bf5 100644 --- a/listings/applications/advanced_factory/src/tests.cairo +++ b/listings/applications/advanced_factory/src/tests.cairo @@ -97,7 +97,7 @@ fn test_create_campaign() { } #[test] -fn test_uprade_campaign_class_hash() { +fn test_upgrade_campaign_class_hash() { let factory = deploy_factory(); let old_class_hash = factory.get_campaign_class_hash(); let new_class_hash = *declare("MockContract").unwrap().contract_class().class_hash; diff --git a/listings/applications/crowdfunding/src/campaign.cairo b/listings/applications/crowdfunding/src/campaign.cairo index e4f0b375..e749f7d7 100644 --- a/listings/applications/crowdfunding/src/campaign.cairo +++ b/listings/applications/crowdfunding/src/campaign.cairo @@ -149,7 +149,7 @@ pub mod Campaign { pub const TRANSFER_FAILED: felt252 = 'Transfer failed'; pub const ZERO_ADDRESS_CALLER: felt252 = 'Caller address zero'; pub const ZERO_ADDRESS_PLEDGER: felt252 = 'Pledger address zero'; - pub const ZERO_ADDRESS_TOKEN: felt252 = 'Token address zerp'; + pub const ZERO_ADDRESS_TOKEN: felt252 = 'Token address zero'; pub const ZERO_DONATION: felt252 = 'Donation must be > 0'; pub const ZERO_GOAL: felt252 = 'Goal must be > 0'; pub const ZERO_PLEDGES: felt252 = 'No pledges to claim'; diff --git a/listings/applications/erc20/src/token.cairo b/listings/applications/erc20/src/token.cairo index 69370439..741ae21d 100644 --- a/listings/applications/erc20/src/token.cairo +++ b/listings/applications/erc20/src/token.cairo @@ -233,7 +233,7 @@ mod tests { const symbols: felt252 = 'mtk'; fn deploy() -> (IERC20Dispatcher, ContractAddress) { - let recipient: ContractAddress = contract_address_const::<'initialzed_recipient'>(); + let recipient: ContractAddress = contract_address_const::<'initialized_recipient'>(); let (contract_address, _) = deploy_syscall( erc20::TEST_CLASS_HASH.try_into().unwrap(), @@ -262,7 +262,7 @@ mod tests { } #[test] fn test_deploy_success() { - let recipient = contract_address_const::<'initialzed_recipient'>(); + let recipient = contract_address_const::<'initialized_recipient'>(); let (_, contract_address) = deploy(); assert_eq!( starknet::testing::pop_log(contract_address), @@ -302,7 +302,7 @@ mod tests { #[test] fn test_balance_of_recipient_deployed() { - let recipient = contract_address_const::<'initialzed_recipient'>(); + let recipient = contract_address_const::<'initialized_recipient'>(); let (dispatcher, _) = deploy(); assert( dispatcher.balance_of(recipient) == initial_supply, 'incorrect balance of recipient' @@ -340,7 +340,7 @@ mod tests { #[test] fn test_approval_success() { - let recipient = contract_address_const::<'initialzed_recipient'>(); + let recipient = contract_address_const::<'initialized_recipient'>(); let spender = contract_address_const::<'spender'>(); let value = 100; let (dispatcher, contract_address) = deploy(); @@ -376,7 +376,7 @@ mod tests { #[test] fn test_should_increase_allowance() { let caller = contract_address_const::<'caller'>(); - let recipient = contract_address_const::<'initialzed_recipient'>(); + let recipient = contract_address_const::<'initialized_recipient'>(); let spender = contract_address_const::<'spender'>(); let amount = 100; let (dispatcher, contract_address) = deploy(); @@ -423,7 +423,7 @@ mod tests { #[test] fn test_should_decrease_allowance() { let caller = contract_address_const::<'caller'>(); - let recipient = contract_address_const::<'initialzed_recipient'>(); + let recipient = contract_address_const::<'initialized_recipient'>(); let spender = contract_address_const::<'spender'>(); let amount = 100; let (dispatcher, contract_address) = deploy(); @@ -462,10 +462,10 @@ mod tests { #[test] #[should_panic(expected: ('ERC20: transfer from 0', 'ENTRYPOINT_FAILED'))] fn test_transfer_when_sender_is_address_zero() { - let reciever = contract_address_const::<'spender'>(); + let receiver = contract_address_const::<'spender'>(); let amount = 100; let (dispatcher, _) = deploy(); - dispatcher.transfer(reciever, amount); + dispatcher.transfer(receiver, amount); } #[test] @@ -473,22 +473,22 @@ mod tests { #[should_panic] fn test_transfer_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); - let reciever = Zero::zero(); + let receiver = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); set_contract_address(caller); - dispatcher.transfer(reciever, amount); + dispatcher.transfer(receiver, amount); } #[test] fn test_transfer_success() { - let caller = contract_address_const::<'initialzed_recipient'>(); - let reciever = contract_address_const::<'receiver'>(); + let caller = contract_address_const::<'initialized_recipient'>(); + let receiver = contract_address_const::<'receiver'>(); let amount = 100; let (dispatcher, contract_address) = deploy(); set_contract_address(caller); - dispatcher.transfer(reciever, amount); - assert_eq!(dispatcher.balance_of(reciever), amount); + dispatcher.transfer(receiver, amount); + assert_eq!(dispatcher.balance_of(receiver), amount); // emits two transfer events assert_eq!( @@ -500,7 +500,7 @@ mod tests { assert_eq!( starknet::testing::pop_log(contract_address), - Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: amount })) + Option::Some(Event::Transfer(Transfer { from: caller, to: receiver, value: amount })) ); } @@ -511,9 +511,9 @@ mod tests { fn test_transferFrom_when_sender_is_address_zero() { let sender = Zero::zero(); let amount = 100; - let reciever = contract_address_const::<'spender'>(); + let receiver = contract_address_const::<'spender'>(); let (dispatcher, _) = deploy(); - dispatcher.transfer_from(sender, reciever, amount); + dispatcher.transfer_from(sender, receiver, amount); } #[test] @@ -521,22 +521,22 @@ mod tests { #[should_panic] fn test_transferFrom_when_recipient_is_address_zero() { let caller = contract_address_const::<'caller'>(); - let reciever = Zero::zero(); + let receiver = Zero::zero(); let amount = 100; let (dispatcher, _) = deploy(); set_contract_address(caller); - dispatcher.transfer_from(caller, reciever, amount); + dispatcher.transfer_from(caller, receiver, amount); } #[test] fn test_transferFrom_success() { - let caller = contract_address_const::<'initialzed_recipient'>(); - let reciever = contract_address_const::<'receiver'>(); + let caller = contract_address_const::<'initialized_recipient'>(); + let receiver = contract_address_const::<'receiver'>(); let amount = 100; let (dispatcher, contract_address) = deploy(); set_contract_address(caller); - dispatcher.transfer_from(caller, reciever, amount); - assert_eq!(dispatcher.balance_of(reciever), amount); + dispatcher.transfer_from(caller, receiver, amount); + assert_eq!(dispatcher.balance_of(receiver), amount); // emits two transfer events @@ -549,7 +549,7 @@ mod tests { assert_eq!( starknet::testing::pop_log(contract_address), - Option::Some(Event::Transfer(Transfer { from: caller, to: reciever, value: amount })) + Option::Some(Event::Transfer(Transfer { from: caller, to: receiver, value: amount })) ); } } diff --git a/listings/applications/upgradeable_contract/src/tests.cairo b/listings/applications/upgradeable_contract/src/tests.cairo index a60886a6..a3ff7ba0 100644 --- a/listings/applications/upgradeable_contract/src/tests.cairo +++ b/listings/applications/upgradeable_contract/src/tests.cairo @@ -71,7 +71,7 @@ mod tests { } #[test] - fn test_succesful_upgrade_from_v0_to_v1() { + fn test_successful_upgrade_from_v0_to_v1() { let (dispatcher_v0, contract_address, _) = deploy_v0(); let (_, _, class_hash) = deploy_v1(); dispatcher_v0.upgrade(class_hash); diff --git a/listings/getting-started/factory/src/simple_factory.cairo b/listings/getting-started/factory/src/simple_factory.cairo index 6b6452d7..3146cdac 100644 --- a/listings/getting-started/factory/src/simple_factory.cairo +++ b/listings/getting-started/factory/src/simple_factory.cairo @@ -39,7 +39,7 @@ pub mod CounterFactory { impl Factory of super::ICounterFactory { // ANCHOR: deploy fn create_counter_at(ref self: ContractState, init_value: u128) -> ContractAddress { - // Contructor arguments + // Constructor arguments let mut constructor_calldata: Array:: = array![init_value.into()]; // Contract deployment diff --git a/listings/getting-started/visibility/src/visibility.cairo b/listings/getting-started/visibility/src/visibility.cairo index 240b6a6c..e97058af 100644 --- a/listings/getting-started/visibility/src/visibility.cairo +++ b/listings/getting-started/visibility/src/visibility.cairo @@ -96,7 +96,7 @@ mod test { assert_eq!(24, state.value.read()); assert_eq!(24, contract.get()); - // We can also acces internal functions from the state + // We can also access internal functions from the state assert_eq!(state._read_value(), state.value.read()); assert_eq!(state._read_value(), contract.get()); } diff --git a/po/messages.pot b/po/messages.po similarity index 100% rename from po/messages.pot rename to po/messages.po diff --git a/src/applications/crowdfunding.md b/src/applications/crowdfunding.md index 5257a78c..68a3d866 100644 --- a/src/applications/crowdfunding.md +++ b/src/applications/crowdfunding.md @@ -10,7 +10,7 @@ Crowdfunding is a method of raising capital through the collective effort of man 6. After the campaign ends, the campaign creator can claim the funds if the campaign goal is reached. 7. Otherwise, campaign did not reach it's goal, pledgers can retrieve their funds. 8. The creator can at any point cancel the campaign for whatever reason and refund all of the pledgers. -9. The contract admin can upgrade the contract implementation, refunding all of the users and reseting the campaign state (we will use this in the [Advanced Factory chapter](./advanced_factory.md)). +9. The contract admin can upgrade the contract implementation, refunding all of the users and resetting the campaign state (we will use this in the [Advanced Factory chapter](./advanced_factory.md)). Because contract upgrades need to be able to refund all of the pledges, we need to be able to iterate over all of the pledgers and their amounts. Since iteration is not supported by `Map`, we need to create a custom storage type that will encompass pledge management. We use a component for this purpose. diff --git a/src/getting-started/basics/syscalls.md b/src/getting-started/basics/syscalls.md index e79a4468..a524a012 100644 --- a/src/getting-started/basics/syscalls.md +++ b/src/getting-started/basics/syscalls.md @@ -4,7 +4,7 @@ At the protocol level, the Starknet Operating System (OS) is the program that ma Some of the OS functionalities are exposed to smart contracts through the use of syscalls (system calls). Syscalls can be used to get information about the state of the Starknet network, to interact with/deploy contracts, emit events, send messages, and perform other low-level operations. -Syscalls return a `SyscallResult` which is either `Sucess` of `Failure`, allowing the contract to handle errors. +Syscalls return a `SyscallResult` which is either `Success` of `Failure`, allowing the contract to handle errors. Here's the available syscalls: diff --git a/src/getting-started/cairo_cheatsheet/enums.md b/src/getting-started/cairo_cheatsheet/enums.md index 9b48967b..c172f689 100644 --- a/src/getting-started/cairo_cheatsheet/enums.md +++ b/src/getting-started/cairo_cheatsheet/enums.md @@ -18,7 +18,7 @@ Enums can be declared both inside and outside a contract. If declared outside, t 2. Enums as parameters and return values to entrypoints - - It is possible to pass `enums` to contract entrypoints as parameters, as well as return them from entrypoints. For that purpose, the enum needs to be serializable and dropable, hence the derivation of traits `Serde` and `Drop` in the above code snippet. + - It is possible to pass `enums` to contract entrypoints as parameters, as well as return them from entrypoints. For that purpose, the enum needs to be serializable and droppable, hence the derivation of traits `Serde` and `Drop` in the above code snippet. Here is an example of a contract illustrating the above statements : diff --git a/src/getting-started/interacting/factory.md b/src/getting-started/interacting/factory.md index 76e503a6..d8b06870 100644 --- a/src/getting-started/interacting/factory.md +++ b/src/getting-started/interacting/factory.md @@ -6,7 +6,7 @@ In the case of smart contracts, we can use this pattern by defining a factory co ## Class hash and contract instance -In Starknet, there's a separation between contract's classes and instances. A contract class serves as a blueprint, defined by the underling Cairo bytecode, contract's entrypoints, ABI and Sierra program hash. The contract class is identified by a class hash. When you want to add a new class to the network, you first need to declare it. +In Starknet, there's a separation between contract's classes and instances. A contract class serves as a blueprint, defined by the underlying Cairo bytecode, contract's entrypoints, ABI and Sierra program hash. The contract class is identified by a class hash. When you want to add a new class to the network, you first need to declare it. When deploying a contract, you need to specify the class hash of the contract you want to deploy. Each instance of a contract has their own storage regardless of the class hash. diff --git a/src/getting-started/interacting/how_to_deploy.md b/src/getting-started/interacting/how_to_deploy.md index 232d97a0..c07c3f31 100644 --- a/src/getting-started/interacting/how_to_deploy.md +++ b/src/getting-started/interacting/how_to_deploy.md @@ -65,7 +65,7 @@ Class hash declared: ``` Check the [Voyager Class Page](https://sepolia.voyager.online/class/0x05c8c21062a74e3c8f2015311d7431e820a08a6b0a9571422b607429112d2eb4). -Now, it's time to deploy the contract. Add the clash hash given above after `--watch`: +Now, it's time to deploy the contract. Add the class hash given above after `--watch`: ```console $ starkli deploy \ From 88ec4a9bdb827bfa122325f316ce311139bd68c1 Mon Sep 17 00:00:00 2001 From: Julio <30329843+julio4@users.noreply.github.com> Date: Wed, 23 Oct 2024 15:55:32 +0700 Subject: [PATCH 34/36] feat: cairo syntax hl (#249) --- CONTRIBUTING.md | 4 +- book.toml | 8 +- po/es.po | 328 +-- po/zh-cn.po | 214 +- .../account_abstraction/account_contract.md | 8 +- .../hash-solidity-compatible.md | 2 +- src/advanced-concepts/hashing.md | 2 +- src/advanced-concepts/library_calls.md | 2 +- .../optimisations/store_using_packing.md | 4 +- .../signature_verification.md | 2 +- src/advanced-concepts/struct-mapping-key.md | 2 +- src/advanced-concepts/write_to_any_slot.md | 2 +- src/applications/advanced_factory.md | 2 +- src/applications/constant-product-amm.md | 2 +- src/applications/crowdfunding.md | 4 +- src/applications/erc20.md | 4 +- src/applications/merkle_tree.md | 2 +- src/applications/nft_dutch_auction.md | 2 +- src/applications/random_number_generator.md | 2 +- src/applications/simple_vault.md | 2 +- src/applications/staking.md | 2 +- src/applications/timelock.md | 2 +- src/applications/upgradeable_contract.md | 4 +- src/components/collisions.md | 6 +- src/components/dependencies.md | 12 +- src/components/how_to.md | 6 +- src/components/ownable.md | 4 +- .../basics/bytearrays-strings.md | 2 +- src/getting-started/basics/constructor.md | 2 +- src/getting-started/basics/counter.md | 2 +- .../basics/custom-types-in-entrypoints.md | 2 +- src/getting-started/basics/documentation.md | 2 +- src/getting-started/basics/errors.md | 6 +- src/getting-started/basics/events.md | 2 +- src/getting-started/basics/mappings.md | 2 +- src/getting-started/basics/storage.md | 4 +- .../basics/storing-custom-types.md | 4 +- src/getting-started/basics/syscalls.md | 30 +- src/getting-started/basics/variables.md | 6 +- .../basics/visibility-mutability.md | 2 +- .../cairo_cheatsheet/arrays.md | 4 +- src/getting-started/cairo_cheatsheet/dict.md | 2 +- src/getting-started/cairo_cheatsheet/enums.md | 4 +- src/getting-started/cairo_cheatsheet/felt.md | 2 +- .../cairo_cheatsheet/if_let.md | 2 +- src/getting-started/cairo_cheatsheet/loop.md | 2 +- .../cairo_cheatsheet/mapping.md | 2 +- src/getting-started/cairo_cheatsheet/match.md | 2 +- .../cairo_cheatsheet/struct.md | 2 +- .../cairo_cheatsheet/tuples.md | 2 +- .../cairo_cheatsheet/type_casting.md | 2 +- src/getting-started/cairo_cheatsheet/while.md | 2 +- .../cairo_cheatsheet/while_let.md | 2 +- .../interacting/calling_other_contracts.md | 4 +- src/getting-started/interacting/factory.md | 2 +- .../interacting/interfaces-traits.md | 6 +- .../testing/contract-testing.md | 8 +- theme/book.js | 1328 ++++++----- theme/css/variables.css | 40 + theme/highlight.js | 2006 ++++++++++++++++- 60 files changed, 3126 insertions(+), 998 deletions(-) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d15852c3..22a64e33 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -200,7 +200,7 @@ c Then, in the markdown file, you can use the following syntax to include only the code between the delimiting comments: ````markdown -````rust +````cairo {{#include ../../listings/path/to/listing/src/contract.cairo:anchor_name}} \``` ```` @@ -215,7 +215,7 @@ b Using `#rustdoc_include` you can have the same result, but users can expand the code in the book to see the whole file (used for showing tests): ````markdown -````rust +````cairo {{#rustdoc_include ../../listings/path/to/listing/src/contract.cairo:anchor_name}} \``` ```` diff --git a/book.toml b/book.toml index 3f1d99d2..e135d0f3 100644 --- a/book.toml +++ b/book.toml @@ -8,13 +8,19 @@ title = "Starknet by Example" [build] extra-watch-dirs = ["listings", "po"] -[preprocessor.gettext] +[preprocessor.cairo] after = ["links"] +[preprocessor.gettext] +after = ["cairo"] + [preprocessor.last-changed] command = "mdbook-last-changed" renderer = ["html"] +[output.html.code.hidelines] +cairo = "# " + [output.html] git-repository-url = "https://github.com/NethermindEth/StarknetByExample" edit-url-template = "https://github.com/NethermindEth/StarknetByExample/edit/main/{path}" diff --git a/po/es.po b/po/es.po index 51cdcf9b..18abf668 100644 --- a/po/es.po +++ b/po/es.po @@ -396,7 +396,7 @@ msgstr "Este es el contrato mínimo que puedes redactar en Cairo:" #: src/getting-started/basics/storage.md:5 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -404,7 +404,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -434,7 +434,7 @@ msgstr "" #: src/getting-started/basics/storage.md:17 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -446,7 +446,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -505,7 +505,7 @@ msgstr "" #: src/getting-started/basics/constructor.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ExampleConstructor {\n" " use starknet::ContractAddress;\n" @@ -525,7 +525,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ExampleConstructor {\n" " use starknet::ContractAddress;\n" @@ -624,7 +624,7 @@ msgstr "Aquí hay un ejemplo simple de un contrato con solo variables locales:" #: src/getting-started/basics/variables.md:23 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ILocalVariablesExample {\n" " fn do_something(self: @TContractState, value: u32) -> u32;\n" @@ -655,7 +655,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ILocalVariablesExample {\n" " fn do_something(self: @TContractState, value: u32) -> u32;\n" @@ -740,7 +740,7 @@ msgstr "" #: src/getting-started/basics/variables.md:62 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStorageVariableExample {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -773,7 +773,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStorageVariableExample {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -855,7 +855,7 @@ msgstr "" #: src/getting-started/basics/variables.md:102 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IGlobalExample {\n" " fn foo(ref self: TContractState);\n" @@ -880,7 +880,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IGlobalExample {\n" " fn foo(ref self: TContractState);\n" @@ -1023,7 +1023,7 @@ msgstr "" #: src/getting-started/basics/visibility-mutability.md:27 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExampleContract {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -1081,7 +1081,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExampleContract {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -1185,7 +1185,7 @@ msgstr "" #: src/getting-started/basics/counter.md:13 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISimpleCounter {\n" " fn get_current_count(self: @TContractState) -> u128;\n" @@ -1227,7 +1227,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISimpleCounter {\n" " fn get_current_count(self: @TContractState) -> u128;\n" @@ -1348,7 +1348,7 @@ msgstr "" #: src/getting-started/basics/mappings.md:13 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -1382,7 +1382,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -1487,7 +1487,7 @@ msgstr "Aquí hay un ejemplo simple que demuestra el uso de estas funciones:" #: src/getting-started/basics/errors.md:18 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IErrorsExample {\n" " fn test_assert(self: @TContractState, i: u256);\n" @@ -1516,7 +1516,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IErrorsExample {\n" " fn test_assert(self: @TContractState, i: u256);\n" @@ -1573,7 +1573,7 @@ msgstr "" #: src/getting-started/basics/errors.md:52 msgid "" -"```rust\n" +"```cairo\n" "mod Errors {\n" " const NOT_POSITIVE: felt252 = 'must be greater than 0';\n" " const NOT_NULL: felt252 = 'must not be null';\n" @@ -1608,7 +1608,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "mod Errors {\n" " const NOT_POSITIVE: felt252 = 'must be greater than 0';\n" " const NOT_NULL: felt252 = 'must not be null';\n" @@ -1671,7 +1671,7 @@ msgstr "" #: src/getting-started/basics/errors.md:91 msgid "" -"```rust\n" +"```cairo\n" "mod VaultErrors {\n" " const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance';\n" "// you can define more errors here\n" @@ -1718,7 +1718,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "mod VaultErrors {\n" " const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance';\n" "// you can define more errors here\n" @@ -1813,7 +1813,7 @@ msgstr "" #: src/getting-started/basics/events.md:8 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IEventCounter {\n" " fn increment(ref self: TContractState);\n" @@ -1876,7 +1876,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IEventCounter {\n" " fn increment(ref self: TContractState);\n" @@ -2029,11 +2029,11 @@ msgstr "#### get_block_hash" #: src/getting-started/basics/syscalls.md:24 msgid "" -"```rust\n" +"```cairo\n" "fn get_block_hash_syscall(block_number: u64) -> SyscallResult\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn get_block_hash_syscall(block_number: u64) -> SyscallResult\n" "```" @@ -2051,12 +2051,12 @@ msgstr "#### get_execution_info" #: src/getting-started/basics/syscalls.md:34 msgid "" -"```rust\n" +"```cairo\n" "fn get_execution_info_syscall() -> SyscallResult>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn get_execution_info_syscall() -> SyscallResult>\n" "```" @@ -2071,7 +2071,7 @@ msgstr "" #: src/getting-started/basics/syscalls.md:41 msgid "" -"```rust\n" +"```cairo\n" "#[derive(Copy, Drop, Debug)]\n" "pub struct ExecutionInfo {\n" " pub block_info: Box,\n" @@ -2127,7 +2127,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[derive(Copy, Drop, Debug)]\n" "pub struct ExecutionInfo {\n" " pub block_info: Box,\n" @@ -2215,14 +2215,14 @@ msgstr "#### call_contract" #: src/getting-started/basics/syscalls.md:104 msgid "" -"```rust\n" +"```cairo\n" "fn call_contract_syscall(\n" " address: ContractAddress, entry_point_selector: felt252, calldata: " "Span\n" ") -> SyscallResult>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn call_contract_syscall(\n" " address: ContractAddress, entry_point_selector: felt252, calldata: " "Span\n" @@ -2262,7 +2262,7 @@ msgstr "#### deploy" #: src/getting-started/basics/syscalls.md:119 msgid "" -"```rust\n" +"```cairo\n" "fn deploy_syscall(\n" " class_hash: ClassHash,\n" " contract_address_salt: felt252,\n" @@ -2271,7 +2271,7 @@ msgid "" ") -> SyscallResult<(ContractAddress, Span::)>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn deploy_syscall(\n" " class_hash: ClassHash,\n" " contract_address_salt: felt252,\n" @@ -2310,7 +2310,7 @@ msgstr "" #: src/getting-started/basics/syscalls.md:135 msgid "" -"```rust\n" +"```cairo\n" "# use starknet::{ContractAddress, ClassHash};\n" "# \n" "# #[starknet::interface]\n" @@ -2387,7 +2387,7 @@ msgid "" "# \n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# use starknet::{ContractAddress, ClassHash};\n" "# \n" "# #[starknet::interface]\n" @@ -2470,13 +2470,13 @@ msgstr "#### emit_event" #: src/getting-started/basics/syscalls.md:206 msgid "" -"```rust\n" +"```cairo\n" "fn emit_event_syscall(\n" " keys: Span, data: Span\n" ") -> SyscallResult<()>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn emit_event_syscall(\n" " keys: Span, data: Span\n" ") -> SyscallResult<()>\n" @@ -2496,7 +2496,7 @@ msgstr "" #: src/getting-started/basics/syscalls.md:216 msgid "" -"```rust\n" +"```cairo\n" "# #[starknet::interface]\n" "# trait IEventCounter {\n" "# fn increment(ref self: TContractState);\n" @@ -2561,7 +2561,7 @@ msgid "" "# \n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# #[starknet::interface]\n" "# trait IEventCounter {\n" "# fn increment(ref self: TContractState);\n" @@ -2658,14 +2658,14 @@ msgstr "#### library_call" #: src/getting-started/basics/syscalls.md:291 msgid "" -"```rust\n" +"```cairo\n" "fn library_call_syscall(\n" " class_hash: ClassHash, function_selector: felt252, calldata: " "Span\n" ") -> SyscallResult>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn library_call_syscall(\n" " class_hash: ClassHash, function_selector: felt252, calldata: " "Span\n" @@ -2694,13 +2694,13 @@ msgstr "#### send_message_to_L1" #: src/getting-started/basics/syscalls.md:304 msgid "" -"```rust\n" +"```cairo\n" "fn send_message_to_l1_syscall(\n" " to_address: felt252, payload: Span\n" ") -> SyscallResult<()>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn send_message_to_l1_syscall(\n" " to_address: felt252, payload: Span\n" ") -> SyscallResult<()>\n" @@ -2721,13 +2721,13 @@ msgstr "#### replace_class" #: src/getting-started/basics/syscalls.md:316 msgid "" -"```rust\n" +"```cairo\n" "fn replace_class_syscall(\n" " class_hash: ClassHash\n" ") -> SyscallResult<()>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn replace_class_syscall(\n" " class_hash: ClassHash\n" ") -> SyscallResult<()>\n" @@ -2747,7 +2747,7 @@ msgstr "" #: src/getting-started/basics/syscalls.md:326 msgid "" -"```rust\n" +"```cairo\n" "# use starknet::class_hash::ClassHash;\n" "# \n" "# #[starknet::interface]\n" @@ -2795,7 +2795,7 @@ msgid "" "# \n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# use starknet::class_hash::ClassHash;\n" "# \n" "# #[starknet::interface]\n" @@ -2864,13 +2864,13 @@ msgstr "#### storage_read" #: src/getting-started/basics/syscalls.md:377 msgid "" -"```rust\n" +"```cairo\n" "fn storage_read_syscall(\n" " address_domain: u32, address: StorageAddress,\n" ") -> SyscallResult\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn storage_read_syscall(\n" " address_domain: u32, address: StorageAddress,\n" ") -> SyscallResult\n" @@ -2899,13 +2899,13 @@ msgstr "#### storage_write" #: src/getting-started/basics/syscalls.md:390 msgid "" -"```rust\n" +"```cairo\n" "fn storage_write_syscall(\n" " address_domain: u32, address: StorageAddress, value: felt252\n" ") -> SyscallResult<()>\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn storage_write_syscall(\n" " address_domain: u32, address: StorageAddress, value: felt252\n" ") -> SyscallResult<()>\n" @@ -2951,7 +2951,7 @@ msgid "" "\n" "#### Gas cost\n" "\n" -"```rust\n" +"```cairo\n" "mod gas_costs {\n" " const STEP: usize = 100;\n" " const RANGE_CHECK: usize = 70;\n" @@ -2989,7 +2989,7 @@ msgstr "" "\n" "#### Gas cost\n" "\n" -"```rust\n" +"```cairo\n" "mod gas_costs {\n" " const STEP: usize = 100;\n" " const RANGE_CHECK: usize = 70;\n" @@ -3046,7 +3046,7 @@ msgstr "" #: src/getting-started/basics/storing-custom-types.md:5 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoringCustomType {\n" " fn set_person(ref self: TContractState, person: Person);\n" @@ -3078,7 +3078,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoringCustomType {\n" " fn set_person(ref self: TContractState, person: Person);\n" @@ -3144,7 +3144,7 @@ msgstr "" #: src/getting-started/basics/custom-types-in-entrypoints.md:6 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISerdeCustomType {\n" " fn person_input(ref self: TContractState, person: SerdeCustomType::" @@ -3176,7 +3176,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISerdeCustomType {\n" " fn person_input(ref self: TContractState, person: SerdeCustomType::" @@ -3267,7 +3267,7 @@ msgstr "" #: src/getting-started/basics/documentation.md:16 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IContract {\n" " /// High-level description of the function\n" @@ -3284,7 +3284,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IContract {\n" " /// High-level description of the function\n" @@ -3430,7 +3430,7 @@ msgstr "## Interfaz explícita" #: src/getting-started/interacting/interfaces-traits.md:18 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExplicitInterfaceContract {\n" " fn get_value(self: @TContractState) -> u32;\n" @@ -3458,7 +3458,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExplicitInterfaceContract {\n" " fn get_value(self: @TContractState) -> u32;\n" @@ -3502,7 +3502,7 @@ msgstr "## Interfaz implícita" #: src/getting-started/interacting/interfaces-traits.md:49 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ImplicitInterfaceContract {\n" " #[storage]\n" @@ -3526,7 +3526,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ImplicitInterfaceContract {\n" " #[storage]\n" @@ -3586,7 +3586,7 @@ msgstr "" #: src/getting-started/interacting/interfaces-traits.md:82 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IImplicitInternalContract {\n" " fn add(ref self: TContractState, nb: u32);\n" @@ -3632,7 +3632,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IImplicitInternalContract {\n" " fn add(ref self: TContractState, nb: u32);\n" @@ -3734,7 +3734,7 @@ msgstr "" #: src/getting-started/interacting/calling_other_contracts.md:12 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ICallee {\n" " fn set_value(ref self: TContractState, value: u128) -> u128;\n" @@ -3757,7 +3757,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ICallee {\n" " fn set_value(ref self: TContractState, value: u128) -> u128;\n" @@ -3796,7 +3796,7 @@ msgstr "" #: src/getting-started/interacting/calling_other_contracts.md:36 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "// We need to have the interface of the callee contract defined\n" @@ -3831,7 +3831,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "// We need to have the interface of the callee contract defined\n" @@ -3953,7 +3953,7 @@ msgstr "" #: src/getting-started/interacting/factory.md:19 msgid "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress, ClassHash};\n" "\n" "#[starknet::interface]\n" @@ -4028,7 +4028,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress, ClassHash};\n" "\n" "#[starknet::interface]\n" @@ -4172,7 +4172,7 @@ msgstr "Empecemos con un simple contrato inteligente como ejemplo:" #: src/getting-started/testing/contract-testing.md:6 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -4216,7 +4216,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -4266,7 +4266,7 @@ msgstr "Ahora, eche un vistazo a las pruebas de este contrato:" #: src/getting-started/testing/contract-testing.md:51 msgid "" -"```rust\n" +"```cairo\n" "#[cfg(test)]\n" "mod tests {\n" " // Import the interface and dispatcher to be able to interact with the " @@ -4354,7 +4354,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[cfg(test)]\n" "mod tests {\n" " // Import the interface and dispatcher to be able to interact with the " @@ -4647,14 +4647,14 @@ msgstr "Por ejemplo:" #: src/getting-started/cairo_cheatsheet/felt.md:8 msgid "" -"```rust\n" +"```cairo\n" " let felt: felt252 = 100;\n" " let felt_as_str = 'Hello Starknet!';\n" "\n" " let felt = felt + felt_as_str;\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let felt: felt252 = 100;\n" " let felt_as_str = 'Hello Starknet!';\n" "\n" @@ -4674,7 +4674,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/mapping.md:5 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -4738,7 +4738,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -4818,7 +4818,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/arrays.md:6 msgid "" -"```rust\n" +"```cairo\n" "trait ArrayTrait {\n" " fn new() -> Array;\n" " fn append(ref self: Array, value: T);\n" @@ -4832,7 +4832,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait ArrayTrait {\n" " fn new() -> Array;\n" " fn append(ref self: Array, value: T);\n" @@ -4848,7 +4848,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/arrays.md:22 msgid "" -"```rust\n" +"```cairo\n" "fn array() -> bool {\n" " let mut arr = ArrayTrait::::new();\n" " arr.append(10);\n" @@ -4868,7 +4868,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn array() -> bool {\n" " let mut arr = ArrayTrait::::new();\n" " arr.append(10);\n" @@ -4904,7 +4904,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/loop.md:6 msgid "" -"```rust\n" +"```cairo\n" " let mut arr = ArrayTrait::new();\n" "\n" " // Same as ~ while (i < 10) arr.append(i++);\n" @@ -4921,7 +4921,7 @@ msgid "" " };\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let mut arr = ArrayTrait::new();\n" "\n" " // Same as ~ while (i < 10) arr.append(i++);\n" @@ -4957,7 +4957,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/match.md:6 msgid "" -"```rust\n" +"```cairo\n" "#[derive(Drop, Serde)]\n" "enum Colour {\n" " Red,\n" @@ -5010,7 +5010,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[derive(Drop, Serde)]\n" "enum Colour {\n" " Red,\n" @@ -5084,7 +5084,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/tuples.md:6 msgid "" -"```rust\n" +"```cairo\n" " let address = \"0x000\";\n" " let age = 20;\n" " let active = true;\n" @@ -5096,7 +5096,7 @@ msgid "" " let (address, age, active) = stored_tuple;\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let address = \"0x000\";\n" " let age = 20;\n" " let active = true;\n" @@ -5124,7 +5124,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/struct.md:6 msgid "" -"```rust\n" +"```cairo\n" "// With Store, you can store Data's structs in the storage part of " "contracts.\n" "#[derive(Drop, starknet::Store)]\n" @@ -5134,7 +5134,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "// With Store, you can store Data's structs in the storage part of " "contracts.\n" "#[derive(Drop, starknet::Store)]\n" @@ -5166,7 +5166,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/type_casting.md:7 msgid "" -"```rust\n" +"```cairo\n" " let a_number: u32 = 15;\n" " let my_felt252 = 15;\n" "\n" @@ -5196,7 +5196,7 @@ msgid "" " let new_usize: usize = my_felt252.try_into().unwrap();\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let a_number: u32 = 15;\n" " let my_felt252 = 15;\n" "\n" @@ -5288,7 +5288,7 @@ msgstr "" #: src/components/how_to.md:19 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISwitchable {\n" " fn is_on(self: @TContractState) -> bool;\n" @@ -5336,7 +5336,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISwitchable {\n" " fn is_on(self: @TContractState) -> bool;\n" @@ -5449,7 +5449,7 @@ msgstr "" #: src/components/how_to.md:86 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod SwitchContract {\n" " use components::switchable::switchable_component;\n" @@ -5539,7 +5539,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod SwitchContract {\n" " use components::switchable::switchable_component;\n" @@ -5660,7 +5660,7 @@ msgstr "Usaremos un nuevo componente `Countable` como ejemplo:" #: src/components/dependencies.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ICountable {\n" " fn get(self: @TContractState) -> u32;\n" @@ -5689,7 +5689,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ICountable {\n" " fn get(self: @TContractState) -> u32;\n" @@ -5744,14 +5744,14 @@ msgstr "Primero definimos el trait `ISwitchable`:" #: src/components/dependencies.md:44 msgid "" -"```rust\n" +"```cairo\n" "trait ISwitchable {\n" " fn is_on(self: @TContractState) -> bool;\n" " fn switch(ref self: TContractState);\n" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait ISwitchable {\n" " fn is_on(self: @TContractState) -> bool;\n" " fn switch(ref self: TContractState);\n" @@ -5768,7 +5768,7 @@ msgstr "" #: src/components/dependencies.md:53 msgid "" -"```rust\n" +"```cairo\n" "# #[starknet::component]\n" "# mod countable_component {\n" "# use components::countable::ICountable;\n" @@ -5798,7 +5798,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# #[starknet::component]\n" "# mod countable_component {\n" "# use components::countable::ICountable;\n" @@ -5838,7 +5838,7 @@ msgstr "" #: src/components/dependencies.md:83 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod CountableContract {\n" " use components_dependencies::countable_dep_switch::countable_component;\n" @@ -5961,7 +5961,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod CountableContract {\n" " use components_dependencies::countable_dep_switch::countable_component;\n" @@ -6111,7 +6111,7 @@ msgstr "" #: src/components/dependencies.md:208 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod CountableContract {\n" " use components_dependencies::countable_dep_switch::countable_component;\n" @@ -6230,7 +6230,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod CountableContract {\n" " use components_dependencies::countable_dep_switch::countable_component;\n" @@ -6398,7 +6398,7 @@ msgstr "" #: src/components/dependencies.md:331 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::component]\n" "mod countable_component {\n" " use components::countable::ICountable;\n" @@ -6464,7 +6464,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::component]\n" "mod countable_component {\n" " use components::countable::ICountable;\n" @@ -6540,7 +6540,7 @@ msgstr "" #: src/components/dependencies.md:394 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod CountableContract {\n" " use components_dependencies::countable_internal_dep_switch::" @@ -6658,7 +6658,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod CountableContract {\n" " use components_dependencies::countable_internal_dep_switch::" @@ -6833,7 +6833,7 @@ msgstr "Interface:" #: src/components/collisions.md:17 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISwitchCollision {\n" " fn set(ref self: TContractState, value: bool);\n" @@ -6841,7 +6841,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISwitchCollision {\n" " fn set(ref self: TContractState, value: bool);\n" @@ -6859,7 +6859,7 @@ msgstr "" #: src/components/collisions.md:26 msgid "" -"```rust\n" +"```cairo\n" "# #[starknet::interface]\n" "# trait ISwitchCollision {\n" "# fn set(ref self: TContractState, value: bool);\n" @@ -6911,7 +6911,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# #[starknet::interface]\n" "# trait ISwitchCollision {\n" "# fn set(ref self: TContractState, value: bool);\n" @@ -6973,7 +6973,7 @@ msgstr "" #: src/components/collisions.md:76 msgid "" -"```rust\n" +"```cairo\n" "# mod switch_collision_tests {\n" "# use components::switchable::switchable_component::" "SwitchableInternalTrait;\n" @@ -7025,7 +7025,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# mod switch_collision_tests {\n" "# use components::switchable::switchable_component::" "SwitchableInternalTrait;\n" @@ -7102,7 +7102,7 @@ msgstr "" #: src/components/ownable.md:7 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -7206,7 +7206,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -7316,7 +7316,7 @@ msgstr "Un contrato simulado que utiliza el componente `Ownable`:" #: src/components/ownable.md:106 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IOwned {\n" " fn do_something(ref self: TContractState);\n" @@ -7498,7 +7498,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IOwned {\n" " fn do_something(ref self: TContractState);\n" @@ -7781,7 +7781,7 @@ msgstr "" #: src/applications/upgradeable_contract.md:25 msgid "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -7827,7 +7827,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -7891,7 +7891,7 @@ msgstr "" #: src/applications/upgradeable_contract.md:71 msgid "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -7937,7 +7937,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -8030,7 +8030,7 @@ msgstr "" #: src/applications/simple_vault.md:10 msgid "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress};\n" "\n" "// In order to make contract calls within our Vault,\n" @@ -8142,7 +8142,7 @@ msgid "" "\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress};\n" "\n" "// In order to make contract calls within our Vault,\n" @@ -8284,7 +8284,7 @@ msgstr "Para crear un contrato ERC20, debe implementar la siguiente interfaz:" #: src/applications/erc20.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IERC20 {\n" " fn get_name(self: @TContractState) -> felt252;\n" @@ -8316,7 +8316,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IERC20 {\n" " fn get_name(self: @TContractState) -> felt252;\n" @@ -8367,7 +8367,7 @@ msgstr "He aquí una implementación de la interfaz ERC20 en Cairo:" #: src/applications/erc20.md:38 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod erc20 {\n" " use zeroable::Zeroable;\n" @@ -8568,7 +8568,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod erc20 {\n" " use zeroable::Zeroable;\n" @@ -8803,7 +8803,7 @@ msgstr "" #: src/applications/constant-product-amm.md:5 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -9102,7 +9102,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -9469,7 +9469,7 @@ msgstr "" #: src/advanced-concepts/write_to_any_slot.md:11 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IWriteToAnySlots {\n" " fn write_slot(ref self: TContractState, value: u32);\n" @@ -9519,7 +9519,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IWriteToAnySlots {\n" " fn write_slot(ref self: TContractState, value: u32);\n" @@ -9638,7 +9638,7 @@ msgstr "" #: src/advanced-concepts/storing_arrays.md:9 msgid "" -"```rust\n" +"```cairo\n" "impl StoreFelt252Array of Store> {\n" " fn read(address_domain: u32, base: StorageBaseAddress) -> " "SyscallResult> {\n" @@ -9711,7 +9711,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "impl StoreFelt252Array of Store> {\n" " fn read(address_domain: u32, base: StorageBaseAddress) -> " "SyscallResult> {\n" @@ -9794,7 +9794,7 @@ msgstr "" #: src/advanced-concepts/storing_arrays.md:75 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoreArrayContract {\n" " fn store_array(ref self: TContractState, arr: Array);\n" @@ -9823,7 +9823,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoreArrayContract {\n" " fn store_array(ref self: TContractState, arr: Array);\n" @@ -9896,7 +9896,7 @@ msgstr "" #: src/advanced-concepts/struct-mapping-key.md:8 msgid "" -"```rust\n" +"```cairo\n" "#[derive(Copy, Drop, Serde, Hash)]\n" "struct Pet {\n" " name: felt252,\n" @@ -9934,7 +9934,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[derive(Copy, Drop, Serde, Hash)]\n" "struct Pet {\n" " name: felt252,\n" @@ -10048,7 +10048,7 @@ msgstr "" #: src/advanced-concepts/hashing.md:14 msgid "" -"```rust\n" +"```cairo\n" "# #[starknet::interface]\n" "# trait IHashTrait {\n" "# fn save_user_with_poseidon(\n" @@ -10177,7 +10177,7 @@ msgid "" "# }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "# #[starknet::interface]\n" "# trait IHashTrait {\n" "# fn save_user_with_poseidon(\n" @@ -10327,7 +10327,7 @@ msgstr "" #: src/advanced-concepts/hash-solidity-compatible.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISolidityHashExample {\n" " fn hash_data(ref self: TContractState, input_data: Span) -> u256;\n" @@ -10367,7 +10367,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISolidityHashExample {\n" " fn hash_data(ref self: TContractState, input_data: Span) -> u256;\n" @@ -10495,14 +10495,14 @@ msgstr "" #: src/advanced-concepts/optimisations/store_using_packing.md:17 msgid "" -"```rust\n" +"```cairo\n" "trait StorePacking {\n" " fn pack(value: T) -> PackedT;\n" " fn unpack(value: PackedT) -> T;\n" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait StorePacking {\n" " fn pack(value: T) -> PackedT;\n" " fn unpack(value: PackedT) -> T;\n" @@ -10533,7 +10533,7 @@ msgstr "" #: src/advanced-concepts/optimisations/store_using_packing.md:28 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ITime {\n" " fn set(ref self: TContractState, value: TimeContract::Time);\n" @@ -10597,7 +10597,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ITime {\n" " fn set(ref self: TContractState, value: TimeContract::Time);\n" @@ -10700,14 +10700,14 @@ msgstr "" #: src/advanced-concepts/list.md:9 msgid "" -"```rust\n" +"```cairo\n" "#[storage]\n" "stuct Storage {\n" " amounts: List\n" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[storage]\n" "stuct Storage {\n" " amounts: List\n" @@ -10720,7 +10720,7 @@ msgstr "### Interface" #: src/advanced-concepts/list.md:18 msgid "" -"```rust\n" +"```cairo\n" "trait ListTrait {\n" " fn len(self: @List) -> u32;\n" " fn is_empty(self: @List) -> bool;\n" @@ -10732,7 +10732,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait ListTrait {\n" " fn len(self: @List) -> u32;\n" " fn is_empty(self: @List) -> bool;\n" @@ -10754,11 +10754,11 @@ msgstr "" #: src/advanced-concepts/list.md:32 msgid "" -"```rust\n" +"```cairo\n" "let second = self.amounts.read()[1];\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "let second = self.amounts.read()[1];\n" "```" @@ -10810,12 +10810,12 @@ msgstr "" #: src/advanced-concepts/list.md:49 msgid "" -"```rust\n" +"```cairo\n" "let mut amounts = self.amounts.read();\n" "amounts.append(42);\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "let mut amounts = self.amounts.read();\n" "amounts.append(42);\n" "```" @@ -10830,14 +10830,14 @@ msgstr "Actualice las dependencias de su proyecto en el archivo `Scarb.toml`:" #: src/advanced-concepts/list.md:57 msgid "" -"```rust\n" +"```cairo\n" "[dependencies]\n" "(...)\n" "alexandria_storage = { git = \"https://github.com/keep-starknet-strange/" "alexandria.git\" }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "[dependencies]\n" "(...)\n" "alexandria_storage = { git = \"https://github.com/keep-starknet-strange/" @@ -10854,7 +10854,7 @@ msgstr "" #: src/advanced-concepts/list.md:65 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IListExample {\n" " fn add_in_amount(ref self: TContractState, number: u128);\n" @@ -10933,7 +10933,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IListExample {\n" " fn add_in_amount(ref self: TContractState, number: u128);\n" diff --git a/po/zh-cn.po b/po/zh-cn.po index 5fea78ee..de215af4 100644 --- a/po/zh-cn.po +++ b/po/zh-cn.po @@ -330,7 +330,7 @@ msgstr "这是您用Cairo能写的最简短的合约:" #: src/getting-started/basics/storage.md:5 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -338,7 +338,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -366,7 +366,7 @@ msgstr "" #: src/getting-started/basics/storage.md:17 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -378,7 +378,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod Contract {\n" " #[storage]\n" @@ -430,7 +430,7 @@ msgstr "" #: src/getting-started/basics/constructor.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ExampleConstructor {\n" " use starknet::ContractAddress;\n" @@ -450,7 +450,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ExampleConstructor {\n" " use starknet::ContractAddress;\n" @@ -546,7 +546,7 @@ msgstr "下面是一个只有局部变量的简单合约示例:" #: src/getting-started/basics/variables.md:23 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ILocalVariablesExample {\n" " fn do_something(self: @TContractState, value: u32) -> u32;\n" @@ -577,7 +577,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ILocalVariablesExample {\n" " fn do_something(self: @TContractState, value: u32) -> u32;\n" @@ -654,7 +654,7 @@ msgstr "下面是一个带有一个存储变量的简单合约示例:" #: src/getting-started/basics/variables.md:62 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStorageVariableExample {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -687,7 +687,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStorageVariableExample {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -766,7 +766,7 @@ msgstr "" #: src/getting-started/basics/variables.md:102 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IGlobalExample {\n" " fn foo(ref self: TContractState);\n" @@ -791,7 +791,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IGlobalExample {\n" " fn foo(ref self: TContractState);\n" @@ -921,7 +921,7 @@ msgstr "让我们通过一个简单的合约示例来了解这些功能:" #: src/getting-started/basics/visibility-mutability.md:27 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExampleContract {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -979,7 +979,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExampleContract {\n" " fn set(ref self: TContractState, value: u32);\n" @@ -1080,7 +1080,7 @@ msgstr "" #: src/getting-started/basics/counter.md:13 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISimpleCounter {\n" " fn get_current_count(self: @TContractState) -> u128;\n" @@ -1122,7 +1122,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISimpleCounter {\n" " fn get_current_count(self: @TContractState) -> u128;\n" @@ -1235,7 +1235,7 @@ msgstr "" #: src/getting-started/basics/mappings.md:13 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -1269,7 +1269,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -1364,7 +1364,7 @@ msgstr "下面是一个简单的示例,演示了这些函数的用法:" #: src/getting-started/basics/errors.md:18 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IErrorsExample {\n" " fn test_assert(self: @TContractState, i: u256);\n" @@ -1393,7 +1393,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IErrorsExample {\n" " fn test_assert(self: @TContractState, i: u256);\n" @@ -1448,7 +1448,7 @@ msgstr "您可以通过在特定模块中定义错误代码来简化错误处理 #: src/getting-started/basics/errors.md:52 msgid "" -"```rust\n" +"```cairo\n" "mod Errors {\n" " const NOT_POSITIVE: felt252 = 'must be greater than 0';\n" " const NOT_NULL: felt252 = 'must not be null';\n" @@ -1482,7 +1482,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "mod Errors {\n" " const NOT_POSITIVE: felt252 = 'must be greater than 0';\n" " const NOT_NULL: felt252 = 'must not be null';\n" @@ -1542,7 +1542,7 @@ msgstr "下面是另一个示例,演示了在更复杂的合约中使用错误 #: src/getting-started/basics/errors.md:91 msgid "" -"```rust\n" +"```cairo\n" "mod VaultErrors {\n" " const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance';\n" "// you can define more errors here\n" @@ -1589,7 +1589,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "mod VaultErrors {\n" " const INSUFFICIENT_BALANCE: felt252 = 'insufficient_balance';\n" "// you can define more errors here\n" @@ -1680,7 +1680,7 @@ msgstr "" #: src/getting-started/basics/events.md:8 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IEventCounter {\n" " fn increment(ref self: TContractState);\n" @@ -1742,7 +1742,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IEventCounter {\n" " fn increment(ref self: TContractState);\n" @@ -1838,7 +1838,7 @@ msgstr "" #: src/getting-started/basics/storing-custom-types.md:5 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoringCustomType {\n" " fn set_person(ref self: TContractState, person: Person);\n" @@ -1870,7 +1870,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoringCustomType {\n" " fn set_person(ref self: TContractState, person: Person);\n" @@ -1933,7 +1933,7 @@ msgstr "" #: src/getting-started/basics/custom-types-in-entrypoints.md:6 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISerdeCustomType {\n" " fn person_input(ref self: TContractState, person: SerdeCustomType::" @@ -1965,7 +1965,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISerdeCustomType {\n" " fn person_input(ref self: TContractState, person: SerdeCustomType::" @@ -2053,7 +2053,7 @@ msgstr "" #: src/getting-started/basics/documentation.md:16 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IContract {\n" " /// High-level description of the function\n" @@ -2070,7 +2070,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IContract {\n" " /// High-level description of the function\n" @@ -2200,7 +2200,7 @@ msgstr "## 显式接口" #: src/getting-started/interacting/interfaces-traits.md:18 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExplicitInterfaceContract {\n" " fn get_value(self: @TContractState) -> u32;\n" @@ -2228,7 +2228,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IExplicitInterfaceContract {\n" " fn get_value(self: @TContractState) -> u32;\n" @@ -2272,7 +2272,7 @@ msgstr "## 隐式接口" #: src/getting-started/interacting/interfaces-traits.md:49 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ImplicitInterfaceContract {\n" " #[storage]\n" @@ -2296,7 +2296,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod ImplicitInterfaceContract {\n" " #[storage]\n" @@ -2355,7 +2355,7 @@ msgstr "" #: src/getting-started/interacting/interfaces-traits.md:82 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IImplicitInternalContract {\n" " fn add(ref self: TContractState, nb: u32);\n" @@ -2401,7 +2401,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IImplicitInternalContract {\n" " fn add(ref self: TContractState, nb: u32);\n" @@ -2500,7 +2500,7 @@ msgstr "" #: src/getting-started/interacting/calling_other_contracts.md:12 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ICallee {\n" " fn set_value(ref self: TContractState, value: u128) -> u128;\n" @@ -2523,7 +2523,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ICallee {\n" " fn set_value(ref self: TContractState, value: u128) -> u128;\n" @@ -2563,7 +2563,7 @@ msgstr "" #: src/getting-started/interacting/calling_other_contracts.md:36 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "// We need to have the interface of the callee contract defined\n" @@ -2598,7 +2598,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "// We need to have the interface of the callee contract defined\n" @@ -2711,7 +2711,7 @@ msgstr "下面是部署`SimpleCounter` 合约的工厂合约的最小范例:" #: src/getting-started/interacting/factory.md:19 msgid "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress, ClassHash};\n" "\n" "#[starknet::interface]\n" @@ -2786,7 +2786,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress, ClassHash};\n" "\n" "#[starknet::interface]\n" @@ -2924,7 +2924,7 @@ msgstr "让我们以一个简单的智能合约作为例子开始:" #: src/getting-started/testing/contract-testing.md:6 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -2968,7 +2968,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -3018,7 +3018,7 @@ msgstr "现在,让我们看一下这个合约的测试:" #: src/getting-started/testing/contract-testing.md:51 msgid "" -"```rust\n" +"```cairo\n" "#[cfg(test)]\n" "mod tests {\n" " // Import the interface and dispatcher to be able to interact with the " @@ -3106,7 +3106,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[cfg(test)]\n" "mod tests {\n" " // Import the interface and dispatcher to be able to interact with the " @@ -3380,14 +3380,14 @@ msgstr "例如:" #: src/getting-started/cairo_cheatsheet/felt.md:8 msgid "" -"```rust\n" +"```cairo\n" " let felt: felt252 = 100;\n" " let felt_as_str = 'Hello Starknet!';\n" "\n" " let felt = felt + felt_as_str;\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let felt: felt252 = 100;\n" " let felt_as_str = ‘Hello Starknet!’;\n" "\n" @@ -3405,7 +3405,7 @@ msgstr "`LegacyMap` 类型可以用于表示键值对的集合。" #: src/getting-started/cairo_cheatsheet/mapping.md:5 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -3469,7 +3469,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -3547,7 +3547,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/arrays.md:6 msgid "" -"```rust\n" +"```cairo\n" "trait ArrayTrait {\n" " fn new() -> Array;\n" " fn append(ref self: Array, value: T);\n" @@ -3561,7 +3561,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait ArrayTrait {\n" " fn new() -> Array;\n" " fn append(ref self: Array, value: T);\n" @@ -3577,7 +3577,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/arrays.md:22 msgid "" -"```rust\n" +"```cairo\n" "fn array() -> bool {\n" " let mut arr = ArrayTrait::::new();\n" " arr.append(10);\n" @@ -3597,7 +3597,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "fn array() -> bool {\n" " let mut arr = ArrayTrait::::new();\n" " arr.append(10);\n" @@ -3632,7 +3632,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/loop.md:6 msgid "" -"```rust\n" +"```cairo\n" " let mut arr = ArrayTrait::new();\n" "\n" " // Same as ~ while (i < 10) arr.append(i++);\n" @@ -3649,7 +3649,7 @@ msgid "" " };\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let mut arr = ArrayTrait::new();\n" "\n" " // 与 ~ while (i < 10) arr.append(i++); 相同\n" @@ -3682,7 +3682,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/match.md:6 msgid "" -"```rust\n" +"```cairo\n" "#[derive(Drop, Serde)]\n" "enum Colour {\n" " Red,\n" @@ -3735,7 +3735,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[derive(Drop, Serde)]\n" "enum Colour {\n" " Red,\n" @@ -3807,7 +3807,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/tuples.md:6 msgid "" -"```rust\n" +"```cairo\n" " let address = \"0x000\";\n" " let age = 20;\n" " let active = true;\n" @@ -3819,7 +3819,7 @@ msgid "" " let (address, age, active) = stored_tuple;\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let address = “0x000”;\n" " let age = 20;\n" " let active = true;\n" @@ -3847,7 +3847,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/struct.md:6 msgid "" -"```rust\n" +"```cairo\n" "// With Store, you can store Data's structs in the storage part of " "contracts.\n" "#[derive(Drop, starknet::Store)]\n" @@ -3857,7 +3857,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "// 使用 Store,您可以将 Data 结构体存储在合约的存储部分。\n" "#[derive(Drop, starknet::Store)]\n" "struct Data {\n" @@ -3886,7 +3886,7 @@ msgstr "" #: src/getting-started/cairo_cheatsheet/type_casting.md:7 msgid "" -"```rust\n" +"```cairo\n" " let a_number: u32 = 15;\n" " let my_felt252 = 15;\n" "\n" @@ -3915,7 +3915,7 @@ msgid "" " let new_usize: usize = my_felt252.try_into().unwrap();\n" "```" msgstr "" -"```rust\n" +"```cairo\n" " let a_number: u32 = 15;\n" " let my_felt252 = 15;\n" "\n" @@ -3981,7 +3981,7 @@ msgstr "" #: src/components/how_to.md:17 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISwitchComponent {\n" " fn value(self: @TContractState) -> bool;\n" @@ -4029,7 +4029,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISwitchComponent {\n" " fn value(self: @TContractState) -> bool;\n" @@ -4109,7 +4109,7 @@ msgstr "" #: src/components/how_to.md:78 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod SwitchContract {\n" " use components::switch::switch_component;\n" @@ -4237,7 +4237,7 @@ msgstr "" #: src/applications/upgradeable_contract.md:25 msgid "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -4282,7 +4282,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -4345,7 +4345,7 @@ msgstr "" #: src/applications/upgradeable_contract.md:71 msgid "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -4390,7 +4390,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::class_hash::ClassHash;\n" "\n" "#[starknet::interface]\n" @@ -4480,7 +4480,7 @@ msgstr "" #: src/applications/simple_vault.md:10 msgid "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress};\n" "\n" "// In order to make contract calls within our Vault,\n" @@ -4592,7 +4592,7 @@ msgid "" "\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::{ContractAddress};\n" "\n" "// In order to make contract calls within our Vault,\n" @@ -4732,7 +4732,7 @@ msgstr "要创建 ERC20 合约,必须实现以下接口:" #: src/applications/erc20.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IERC20 {\n" " fn get_name(self: @TContractState) -> felt252;\n" @@ -4764,7 +4764,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IERC20 {\n" " fn get_name(self: @TContractState) -> felt252;\n" @@ -4813,7 +4813,7 @@ msgstr "以下是一个在Cairo中实现的ERC20接口的示例:" #: src/applications/erc20.md:38 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod erc20 {\n" " use zeroable::Zeroable;\n" @@ -5014,7 +5014,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::contract]\n" "mod erc20 {\n" " use zeroable::Zeroable;\n" @@ -5249,7 +5249,7 @@ msgstr "" #: src/applications/constant-product-amm.md:5 msgid "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -5548,7 +5548,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "use starknet::ContractAddress;\n" "\n" "#[starknet::interface]\n" @@ -5904,7 +5904,7 @@ msgstr "" #: src/advanced-concepts/write_to_any_slot.md:11 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IWriteToAnySlots {\n" " fn write_slot(ref self: TContractState, value: u32);\n" @@ -5954,7 +5954,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IWriteToAnySlots {\n" " fn write_slot(ref self: TContractState, value: u32);\n" @@ -6065,7 +6065,7 @@ msgstr "" #: src/advanced-concepts/storing_arrays.md:9 msgid "" -"```rust\n" +"```cairo\n" "impl StoreFelt252Array of Store> {\n" " fn read(address_domain: u32, base: StorageBaseAddress) -> " "SyscallResult> {\n" @@ -6137,7 +6137,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "impl StoreFelt252Array of Store> {\n" " fn read(address_domain: u32, base: StorageBaseAddress) -> " "SyscallResult> {\n" @@ -6217,7 +6217,7 @@ msgstr "您可以在合约中导入上面的实现方式,并使用它来在存 #: src/advanced-concepts/storing_arrays.md:75 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoreArrayContract {\n" " fn store_array(ref self: TContractState, arr: Array);\n" @@ -6246,7 +6246,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IStoreArrayContract {\n" " fn store_array(ref self: TContractState, arr: Array);\n" @@ -6315,7 +6315,7 @@ msgstr "" #: src/advanced-concepts/struct-mapping-key.md:8 msgid "" -"```rust\n" +"```cairo\n" "#[derive(Copy, Drop, Serde, Hash)]\n" "struct Pet {\n" " name: felt252,\n" @@ -6352,7 +6352,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[derive(Copy, Drop, Serde, Hash)]\n" "struct Pet {\n" " name: felt252,\n" @@ -6419,7 +6419,7 @@ msgstr "" #: src/advanced-concepts/hash-solidity-compatible.md:7 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISolidityHashExample {\n" " fn hash_data(ref self: TContractState, input_data: Span) -> u256;\n" @@ -6458,7 +6458,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ISolidityHashExample {\n" " fn hash_data(ref self: TContractState, input_data: Span) -> u256;\n" @@ -6577,14 +6577,14 @@ msgstr "" #: src/advanced-concepts/optimisations/store_using_packing.md:17 msgid "" -"```rust\n" +"```cairo\n" "trait StorePacking {\n" " fn pack(value: T) -> PackedT;\n" " fn unpack(value: PackedT) -> T;\n" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait StorePacking {\n" " fn pack(value: T) -> PackedT;\n" " fn unpack(value: PackedT) -> T;\n" @@ -6612,7 +6612,7 @@ msgstr "" #: src/advanced-concepts/optimisations/store_using_packing.md:28 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ITime {\n" " fn set(ref self: TContractState, value: TimeContract::Time);\n" @@ -6676,7 +6676,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait ITime {\n" " fn set(ref self: TContractState, value: TimeContract::Time);\n" @@ -6776,14 +6776,14 @@ msgstr "可以在 Starknet 存储中使用的有序值序列:" #: src/advanced-concepts/list.md:9 msgid "" -"```rust\n" +"```cairo\n" "#[storage]\n" "stuct Storage {\n" " amounts: List\n" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[storage]\n" "stuct Storage {\n" " amounts: List\n" @@ -6796,7 +6796,7 @@ msgstr "### 接口" #: src/advanced-concepts/list.md:18 msgid "" -"```rust\n" +"```cairo\n" "trait ListTrait {\n" " fn len(self: @List) -> u32;\n" " fn is_empty(self: @List) -> bool;\n" @@ -6808,7 +6808,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "trait ListTrait {\n" " fn len(self: @List) -> u32;\n" " fn is_empty(self: @List) -> bool;\n" @@ -6829,11 +6829,11 @@ msgstr "" #: src/advanced-concepts/list.md:32 msgid "" -"```rust\n" +"```cairo\n" "let second = self.amounts.read()[1];\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "let second = self.amounts.read()[1];\n" "```" @@ -6883,12 +6883,12 @@ msgstr "" #: src/advanced-concepts/list.md:49 msgid "" -"```rust\n" +"```cairo\n" "let mut amounts = self.amounts.read();\n" "amounts.append(42);\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "let mut amounts = self.amounts.read();\n" "amounts.append(42);\n" "```" @@ -6903,14 +6903,14 @@ msgstr "在 `Scarb.toml` 里更新您项目的依赖:" #: src/advanced-concepts/list.md:57 msgid "" -"```rust\n" +"```cairo\n" "[dependencies]\n" "(...)\n" "alexandria_storage = { git = \"https://github.com/keep-starknet-strange/" "alexandria.git\" }\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "[dependencies]\n" "(...)\n" "alexandria_storage = { git = \"https://github.com/keep-starknet-strange/" @@ -6925,7 +6925,7 @@ msgstr "例如,我们用 `List` 来创建一个跟踪`amount`和`tasks`的合 #: src/advanced-concepts/list.md:65 msgid "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IListExample {\n" " fn add_in_amount(ref self: TContractState, number: u128);\n" @@ -7004,7 +7004,7 @@ msgid "" "}\n" "```" msgstr "" -"```rust\n" +"```cairo\n" "#[starknet::interface]\n" "trait IListExample {\n" " fn add_in_amount(ref self: TContractState, number: u128);\n" diff --git a/src/advanced-concepts/account_abstraction/account_contract.md b/src/advanced-concepts/account_abstraction/account_contract.md index 1cd8dde9..d7c2444e 100644 --- a/src/advanced-concepts/account_abstraction/account_contract.md +++ b/src/advanced-concepts/account_abstraction/account_contract.md @@ -5,7 +5,7 @@ In practice, this means that the contract must implement the `SRC6` and `SRC5` i ## SNIP-6: SRC6 + SRC5 -```rust +```cairo /// @title Represents a call to a target contract /// @param to The target contract address /// @param selector The target function selector @@ -19,7 +19,7 @@ struct Call { The `Call` struct is used to represent a call to a function (`selector`) in a target contract (`to`) with parameters (`calldata`). It is available under the `starknet::account` module. -```rust +```cairo /// @title SRC-6 Standard Account trait ISRC6 { /// @notice Execute a transaction through the account @@ -52,7 +52,7 @@ Both `__execute__` and `__validate__` functions are exclusively called by the St -```rust +```cairo /// @title SRC-5 Standard Interface Detection trait ISRC5 { /// @notice Query if a contract implements an interface @@ -68,6 +68,6 @@ The interface identifiers of both `SRC5` and `SRC6` must be published with `supp In this example, we will implement a minimal account contract that can validate and execute transactions. -```rust +```cairo {{#rustdoc_include ../../../listings/advanced-concepts/simple_account/src/simple_account.cairo}} ``` \ No newline at end of file diff --git a/src/advanced-concepts/hash-solidity-compatible.md b/src/advanced-concepts/hash-solidity-compatible.md index c2a7b336..7ffc7e8b 100644 --- a/src/advanced-concepts/hash-solidity-compatible.md +++ b/src/advanced-concepts/hash-solidity-compatible.md @@ -4,6 +4,6 @@ This contract demonstrates Keccak hashing in Cairo to match Solidity's keccak256 For example: -```rust +```cairo {{#include ../../listings/advanced-concepts/hash_solidity_compatible/src/contract.cairo}} ``` diff --git a/src/advanced-concepts/hashing.md b/src/advanced-concepts/hashing.md index 54c233d8..47394a17 100644 --- a/src/advanced-concepts/hashing.md +++ b/src/advanced-concepts/hashing.md @@ -11,6 +11,6 @@ In Cairo, it's possible to hash all types that can be converted to `felt252` sin To hash a value, you first need to initialize a hash state with the `new` method of the `HashStateTrait`. Then, you can update the hash state with the `update` method. You can accumulate multiple updates if necessary. Finally, the `finalize` method returns the final hash value as a `felt252`. -```rust +```cairo {{#rustdoc_include ../../listings/advanced-concepts/hash_trait/src/hash_trait.cairo:hash}} ``` diff --git a/src/advanced-concepts/library_calls.md b/src/advanced-concepts/library_calls.md index 40460cb8..5c87b006 100644 --- a/src/advanced-concepts/library_calls.md +++ b/src/advanced-concepts/library_calls.md @@ -8,6 +8,6 @@ Contract dispatcher call is synonymous to external calls in Solidity, while libr For further reading: [Cairo book](https://book.cairo-lang.org/ch15-03-executing-code-from-another-class.html#library-calls) -```rust +```cairo {{#rustdoc_include ../../listings/advanced-concepts/library_calls/src/library_call.cairo:library_dispatcher}} ``` diff --git a/src/advanced-concepts/optimisations/store_using_packing.md b/src/advanced-concepts/optimisations/store_using_packing.md index c9317604..28709f0c 100644 --- a/src/advanced-concepts/optimisations/store_using_packing.md +++ b/src/advanced-concepts/optimisations/store_using_packing.md @@ -14,7 +14,7 @@ For example, if we want to store two `u8` values, we can use the first 8 bits of Cairo provides a built-in store using packing that you can use with the `StorePacking` trait. -```rust +```cairo trait StorePacking { fn pack(value: T) -> PackedT; fn unpack(value: PackedT) -> T; @@ -25,6 +25,6 @@ This allows us to store the type `T` by first packing it into the type `PackedT` Here's an example of storing a `Time` struct with two `u8` values using the `StorePacking` trait: -```rust +```cairo {{#include ../../../listings/advanced-concepts/store_using_packing/src/contract.cairo}} ``` diff --git a/src/advanced-concepts/signature_verification.md b/src/advanced-concepts/signature_verification.md index 278cb1be..233ea1f6 100644 --- a/src/advanced-concepts/signature_verification.md +++ b/src/advanced-concepts/signature_verification.md @@ -3,6 +3,6 @@ This is the Cairo adaptation of the [Solidity by Example - Verifying Signature](https://solidity-by-example.org/signature/). Messages can be signed off chain and then verified on chain using a smart contract. -```rust +```cairo {{#rustdoc_include ../../listings/advanced-concepts/ecdsa_verification/src/ecdsa_verification.cairo:contract}} ``` diff --git a/src/advanced-concepts/struct-mapping-key.md b/src/advanced-concepts/struct-mapping-key.md index 72773110..7b138963 100644 --- a/src/advanced-concepts/struct-mapping-key.md +++ b/src/advanced-concepts/struct-mapping-key.md @@ -5,6 +5,6 @@ In order to use structs as mapping keys, you can use `#[derive(Hash)]` on the st Consider the following example in which we would like to use an object of type `Pet` as a key in a `Map`. The `Pet` struct has three fields: `name`, `age` and `owner`. We consider that the combination of these three fields uniquely identifies a pet. -```rust +```cairo {{#include ../../listings/advanced-concepts/struct_as_mapping_key/src/contract.cairo}} ``` diff --git a/src/advanced-concepts/write_to_any_slot.md b/src/advanced-concepts/write_to_any_slot.md index ef957757..c0c89a12 100644 --- a/src/advanced-concepts/write_to_any_slot.md +++ b/src/advanced-concepts/write_to_any_slot.md @@ -8,6 +8,6 @@ This is useful when writing to storage variables that are not known at compile t In the following example, we use the Poseidon hash function to compute the address of a storage variable. Poseidon is a ZK-friendly hash function that is cheaper and faster than Pedersen, making it an excellent choice for onchain computations. Once the address is computed, we use the storage syscalls to interact with it. -```rust +```cairo {{#include ../../listings/advanced-concepts/write_to_any_slot/src/contract.cairo}} ``` diff --git a/src/applications/advanced_factory.md b/src/applications/advanced_factory.md index 5d2a27fd..f223f7c7 100644 --- a/src/applications/advanced_factory.md +++ b/src/applications/advanced_factory.md @@ -8,6 +8,6 @@ Key Features 3. **Upgrade Mechanism**: The factory owner can update the implementation of the campaign contract, ensuring that all campaigns benefit from improvements and bug fixes. - the factory only updates it's `Campaign` class hash and emits an event to notify any listeners, but the `Campaign` creators are in the end responsible for actually upgrading their contracts. -```rust +```cairo {{#include ../../listings/applications/advanced_factory/src/contract.cairo:contract}} ``` diff --git a/src/applications/constant-product-amm.md b/src/applications/constant-product-amm.md index 2ec959da..c5adb16e 100644 --- a/src/applications/constant-product-amm.md +++ b/src/applications/constant-product-amm.md @@ -2,6 +2,6 @@ This is the Cairo adaptation of the [Solidity by Example - Constant Product AMM](https://solidity-by-example.org/defi/constant-product-amm/). -```rust +```cairo {{#include ../../listings/applications/constant_product_amm/src/contracts.cairo:ConstantProductAmmContract}} ``` diff --git a/src/applications/crowdfunding.md b/src/applications/crowdfunding.md index 68a3d866..2f10c54a 100644 --- a/src/applications/crowdfunding.md +++ b/src/applications/crowdfunding.md @@ -14,13 +14,13 @@ Crowdfunding is a method of raising capital through the collective effort of man Because contract upgrades need to be able to refund all of the pledges, we need to be able to iterate over all of the pledgers and their amounts. Since iteration is not supported by `Map`, we need to create a custom storage type that will encompass pledge management. We use a component for this purpose. -```rust +```cairo {{#include ../../listings/applications/crowdfunding/src/campaign/pledgeable.cairo:component}} ``` Now we can create the `Campaign` contract. -```rust +```cairo {{#include ../../listings/applications/crowdfunding/src/campaign.cairo:contract}} ``` diff --git a/src/applications/erc20.md b/src/applications/erc20.md index af0085ac..6b2ac43a 100644 --- a/src/applications/erc20.md +++ b/src/applications/erc20.md @@ -4,7 +4,7 @@ Contracts that follow the [ERC20 Standard](https://eips.ethereum.org/EIPS/eip-20 To create an ERC20 contract, it must implement the following interface: -```rust +```cairo {{#include ../../listings/applications/erc20/src/token.cairo:interface}} ``` @@ -13,7 +13,7 @@ The Starknet ERC20 interface is therefore slightly different from the Solidity E Here's an implementation of the ERC20 interface in Cairo: -```rust +```cairo {{#include ../../listings/applications/erc20/src/token.cairo:erc20}} ``` diff --git a/src/applications/merkle_tree.md b/src/applications/merkle_tree.md index e1d30bd3..8194949e 100644 --- a/src/applications/merkle_tree.md +++ b/src/applications/merkle_tree.md @@ -61,6 +61,6 @@ If we were to `verify` the `hash 6`, the merkle proof would need to contain the The following implementation is the Cairo adaptation of the [Solidity by Example - Merkle Tree contract](https://solidity-by-example.org/app/merkle-tree/). -```rust +```cairo {{#include ../../listings/applications/merkle_tree/src/contract.cairo}} ``` diff --git a/src/applications/nft_dutch_auction.md b/src/applications/nft_dutch_auction.md index 7df20588..4482bb3b 100644 --- a/src/applications/nft_dutch_auction.md +++ b/src/applications/nft_dutch_auction.md @@ -10,6 +10,6 @@ Here's how it works: - Participants can purchase NFTs at any time as long as the totalSupply has not been reached. - The auction ends when either the totalSupply is reached or the duration has elapsed. -```rust +```cairo {{#rustdoc_include ../../listings/applications/nft_dutch_auction/src/nft_dutch_auction.cairo:contract}} ``` diff --git a/src/applications/random_number_generator.md b/src/applications/random_number_generator.md index 8b7c5990..c084b911 100644 --- a/src/applications/random_number_generator.md +++ b/src/applications/random_number_generator.md @@ -59,6 +59,6 @@ Below is an implementation of a `CoinFlip` contract that utilizes a [Pragma Veri - Randomness is generated off-chain, and then submitted to the contract using the `receive_random_words` callback - Based on this random value, the contract determines whether the coin "landed" on `Heads` or on `Tails`, and the `Landed` event is emitted -```rust +```cairo {{#include ../../listings/applications/coin_flip/src/contract.cairo}} ``` diff --git a/src/applications/simple_vault.md b/src/applications/simple_vault.md index 749686c5..1a6015ec 100644 --- a/src/applications/simple_vault.md +++ b/src/applications/simple_vault.md @@ -7,6 +7,6 @@ Here's how it works: - When a user withdraws, the contract burns their shares, calculates the yield, and withdraws both the yield and the initial amount of tokens deposited. -```rust +```cairo {{#include ../../listings/applications/simple_vault/src/simple_vault.cairo}} ``` diff --git a/src/applications/staking.md b/src/applications/staking.md index 2a0eb83d..7c685ce3 100644 --- a/src/applications/staking.md +++ b/src/applications/staking.md @@ -28,6 +28,6 @@ The following staking contract is designed to allow users to stake tokens in exc The following implementation is the Cairo adaptation of the [Solidity by Example - Staking Rewards contract](https://solidity-by-example.org/defi/staking-rewards/). It includes a small adaptation to keep track of the amount of total distributed reward tokens and emit an event when the remaining reward token amount reaches 0. -```rust +```cairo {{#include ../../listings/applications/staking/src/contract.cairo}} ``` diff --git a/src/applications/timelock.md b/src/applications/timelock.md index aaf73d08..24e92662 100644 --- a/src/applications/timelock.md +++ b/src/applications/timelock.md @@ -2,6 +2,6 @@ This is the Cairo adaptation of the [Solidity by example TimeLock](https://solidity-by-example.org/app/time-lock/). -```rust +```cairo {{#include ../../listings/applications/timelock/src/timelock.cairo}} ``` diff --git a/src/applications/upgradeable_contract.md b/src/applications/upgradeable_contract.md index c456a82b..95432037 100644 --- a/src/applications/upgradeable_contract.md +++ b/src/applications/upgradeable_contract.md @@ -22,10 +22,10 @@ You can upgrade a deployed contract to a newer version by calling the `replace_c To illustrate this concept, let's consider an example with two contracts: `UpgradeableContract_V0`, and `UpgradeableContract_V1`. Start by deploying `UpgradeableContract_V0` as the initial version. Next, send a transaction that invokes the `upgrade` function, with the class hash of `UpgradeableContract_V1` as parameter to upgrade the class hash of the deployed contract to the `UpgradeableContract_V1` one. Then, call the `version` method on the contract to see that the contract was upgraded to the V1 version. -```rust +```cairo {{#include ../../listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo:contract}} ``` -```rust +```cairo {{#include ../../listings/applications/upgradeable_contract/src/upgradeable_contract_v1.cairo}} ``` diff --git a/src/components/collisions.md b/src/components/collisions.md index 98bf9448..9982c0f2 100644 --- a/src/components/collisions.md +++ b/src/components/collisions.md @@ -15,18 +15,18 @@ Here's an example of a collision on the `switchable_value` storage variable of t Interface: -```rust +```cairo {{#include ../../listings/applications/components/src/others/switch_collision.cairo:interface}} ``` Here's the storage of the contract (you can expand the code snippet to see the full contract and tests): -```rust +```cairo {{#rustdoc_include ../../listings/applications/components/src/others/switch_collision.cairo:storage}} ``` Both the contract and the component have a `switchable_value` storage variable, so they collide: -```rust +```cairo {{#include ../../listings/applications/components/src/others/switch_collision.cairo:collision}} ``` diff --git a/src/components/dependencies.md b/src/components/dependencies.md index 31431aca..b96f28d5 100644 --- a/src/components/dependencies.md +++ b/src/components/dependencies.md @@ -4,7 +4,7 @@ A component with a dependency on a trait `T` can be used in a contract as long a We will use a new `Countable` component as an example: -```rust +```cairo {{#rustdoc_include ../../listings/applications/components/src/countable.cairo:component}} ``` @@ -16,19 +16,19 @@ Instead, we add the trait `Switchable` as a dependency to the `Countable` compon First, we import the `ISwitchable` trait defined in chapter ["Components How-To"](./how_to.md): -```rust +```cairo {{#include ../../listings/applications/components/src/switchable.cairo:interface}} ``` Then we can modify the implementation of the `Countable` component to depend on the `ISwitchable` trait: -```rust +```cairo {{#rustdoc_include ../../listings/applications/components_dependencies/src/countable_dep_switch.cairo:impl}} ``` A contract that uses the `Countable` component must implement the `ISwitchable` trait: -```rust +```cairo {{#rustdoc_include ../../listings/applications/components_dependencies/src/contract_countable.cairo:contract}} ``` @@ -39,7 +39,7 @@ In the previous example, we implemented the `ISwitchable` trait in the contract. We already implemented a [`Switchable`](./how_to.md) component that provides an implementation of the `ISwitchable` trait. By using the `Switchable` component in a contract, we can embed the implementation of the `ISwitchable` trait in the contract and resolve the dependency on the `ISwitchable` trait. -```rust +```cairo {{#rustdoc_include ../../listings/applications/components_dependencies/src/contract_countable_switchable.cairo:contract}} ``` @@ -54,7 +54,7 @@ We can't embed `SwitchableInternalImpl`, but we can add `switchable::HasComponen We make the `Countable` component depend on the `Switchable` component. This will allow to do `switchable::ComponentState` -> `TContractState` -> `countable::ComponentState` and access the internal functions of the `Switchable` component inside the `Countable` component: -```rust +```cairo {{#rustdoc_include ../../listings/applications/components_dependencies/src/countable_internal_dep_switch.cairo:contract}} ``` diff --git a/src/components/how_to.md b/src/components/how_to.md index 819954f1..50220403 100644 --- a/src/components/how_to.md +++ b/src/components/how_to.md @@ -17,7 +17,7 @@ It contains a storage variable `switchable_value`, a function `switch` and an ev > It is a good practice to prefix the component storage variables with the component name to [avoid collisions](./collisions.md). -```rust +```cairo {{#include ../../listings/applications/components/src/switchable.cairo:component}} ``` @@ -41,7 +41,7 @@ It doesn't have a constructor, but you can create an `_init` internal function a Now that we have a component, we can use it in a contract. The following contract incorporates the `Switchable` component: -```rust +```cairo {{#include ../../listings/applications/components/src/switchable.cairo:contract}} ``` @@ -52,7 +52,7 @@ A common practice is to declare a `Mock` contract that has the only purpose of t To test the `Switchable` component, we can use the previous `SwitchableContract`: -```rust +```cairo {{#include ../../listings/applications/components/src/switchable.cairo:tests}} ``` diff --git a/src/components/ownable.md b/src/components/ownable.md index 20d8f313..ead71ade 100644 --- a/src/components/ownable.md +++ b/src/components/ownable.md @@ -4,12 +4,12 @@ The following `Ownable` component is a simple component that allows the contract It can also be used to renounce ownership of a contract, meaning that no one will be able to satisfy the `_assert_is_owner` function. -```rust +```cairo {{#include ../../listings/applications/components/src/ownable.cairo:component}} ``` A mock contract that uses the `Ownable` component: -```rust +```cairo {{#rustdoc_include ../../listings/applications/components/src/ownable.cairo:contract}} ``` diff --git a/src/getting-started/basics/bytearrays-strings.md b/src/getting-started/basics/bytearrays-strings.md index 5b882065..b70fab07 100644 --- a/src/getting-started/basics/bytearrays-strings.md +++ b/src/getting-started/basics/bytearrays-strings.md @@ -19,7 +19,7 @@ ByteArrays are declared with double quotes, like this: `"Hello, World!"`. They can be stored in the contract's storage and passed as arguments to entrypoints. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/bytearray/src/bytearray.cairo:contract}} ``` diff --git a/src/getting-started/basics/constructor.md b/src/getting-started/basics/constructor.md index 630a6130..2c92a1dc 100644 --- a/src/getting-started/basics/constructor.md +++ b/src/getting-started/basics/constructor.md @@ -4,6 +4,6 @@ Constructors are a special type of function that runs only once when deploying a Here's a simple example that demonstrates how to initialize the state of a contract on deployment by defining logic inside a constructor. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/constructor/src/constructor.cairo:contract}} ``` diff --git a/src/getting-started/basics/counter.md b/src/getting-started/basics/counter.md index 8db67e55..266d70b0 100644 --- a/src/getting-started/basics/counter.md +++ b/src/getting-started/basics/counter.md @@ -10,6 +10,6 @@ Here's how it works: - When a user calls the `decrement`, the contract decrements `counter` by `1`. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/counter/src/counter.cairo:contract}} ``` diff --git a/src/getting-started/basics/custom-types-in-entrypoints.md b/src/getting-started/basics/custom-types-in-entrypoints.md index c3bf484d..7603dfe7 100644 --- a/src/getting-started/basics/custom-types-in-entrypoints.md +++ b/src/getting-started/basics/custom-types-in-entrypoints.md @@ -3,7 +3,7 @@ Using custom types in entrypoints requires our type to implement the `Serde` trait. This is because when calling an entrypoint, the input is sent as an array of `felt252` to the entrypoint, and we need to be able to deserialize it into our custom type. Similarly, when returning a custom type from an entrypoint, we need to be able to serialize it into an array of `felt252`. Thankfully, we can just derive the `Serde` trait for our custom type. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/custom_type_serde/src/contract.cairo:contract}} ``` diff --git a/src/getting-started/basics/documentation.md b/src/getting-started/basics/documentation.md index ff8a2eb3..3d425691 100644 --- a/src/getting-started/basics/documentation.md +++ b/src/getting-started/basics/documentation.md @@ -13,7 +13,7 @@ Since Cairo 1, the community has adopted a [Rust-like documentation style](https In smart contracts, you will often have a trait that defines the contract's interface (with `#[starknet::interface]`). This is the perfect place to include detailed documentation explaining the purpose and functionality of the contract entry points. You can follow this template: -```rust +```cairo #[starknet::interface] trait IContract { /// High-level description of the function diff --git a/src/getting-started/basics/errors.md b/src/getting-started/basics/errors.md index 31006283..ce6ea155 100644 --- a/src/getting-started/basics/errors.md +++ b/src/getting-started/basics/errors.md @@ -17,7 +17,7 @@ The `assert_eq!`, `assert_ne!`, `assert_lt!`, `assert_le!`, `assert_gt!` and `as Here's a simple example that demonstrates the use of these functions: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/errors/src/simple_errors.cairo:contract}} ``` @@ -25,7 +25,7 @@ Here's a simple example that demonstrates the use of these functions: You can make error handling easier by defining your error codes in a specific module. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/errors/src/custom_errors.cairo:contract}} ``` @@ -33,6 +33,6 @@ You can make error handling easier by defining your error codes in a specific mo Here's another example that demonstrates the use of errors in a more complex contract: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/errors/src/vault_errors.cairo:contract}} ``` diff --git a/src/getting-started/basics/events.md b/src/getting-started/basics/events.md index 8b6ca2a5..dc28219e 100644 --- a/src/getting-started/basics/events.md +++ b/src/getting-started/basics/events.md @@ -5,6 +5,6 @@ An event is defined as a struct that derives the `starknet::Event` trait. The fi Here's a simple example of a contract that emits an event each time a counter is incremented by the `increment` function: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/events/src/counter.cairo:contract}} ``` diff --git a/src/getting-started/basics/mappings.md b/src/getting-started/basics/mappings.md index d52a87f7..6905de1b 100644 --- a/src/getting-started/basics/mappings.md +++ b/src/getting-started/basics/mappings.md @@ -10,6 +10,6 @@ Some additional notes: - In mappings, the address of the value at key `k_1,...,k_n` is `h(...h(h(sn_keccak(variable_name),k_1),k_2),...,k_n)` where `ℎ` is the Pedersen hash and the final value is taken \\( \bmod {2^{251}} - 256 \\). You can learn more about the contract storage layout in the [Starknet Documentation](https://docs.starknet.io/documentation/architecture_and_concepts/Smart_Contracts/contract-storage/#storage_variables). -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/mappings/src/mappings.cairo:contract}} ``` diff --git a/src/getting-started/basics/storage.md b/src/getting-started/basics/storage.md index b3f7f3e5..3aee55b5 100644 --- a/src/getting-started/basics/storage.md +++ b/src/getting-started/basics/storage.md @@ -2,7 +2,7 @@ Here's the most minimal contract you can write in Cairo: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/storage/src/minimal_contract.cairo:contract}} ``` @@ -11,7 +11,7 @@ It's a key-value store, where each key will be mapped to a storage address of th You can define [storage variables](./variables.md#storage-variables) in your contract, and then use them to store and retrieve data. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/storage/src/contract.cairo:contract}} ``` diff --git a/src/getting-started/basics/storing-custom-types.md b/src/getting-started/basics/storing-custom-types.md index e46c7baa..59987528 100644 --- a/src/getting-started/basics/storing-custom-types.md +++ b/src/getting-started/basics/storing-custom-types.md @@ -2,13 +2,13 @@ While native types can be stored in a contract's storage without any additional work, custom types require a bit more work. This is because at compile time, the compiler does not know how to store custom types in storage. To solve this, we need to implement the `Store` trait for our custom type. It is enough to just derive this trait, unless our custom type contains arrays or dictionaries. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/storing_custom_types/src/contract.cairo:contract}} ``` Note that it is also possible to individually access the members of the stored struct. This is possible because deriving the `Store` trait also generates the corresponding `StoragePointer` for each member. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/storing_custom_types/src/contract.cairo:set_name}} ``` diff --git a/src/getting-started/basics/syscalls.md b/src/getting-started/basics/syscalls.md index a524a012..3957becd 100644 --- a/src/getting-started/basics/syscalls.md +++ b/src/getting-started/basics/syscalls.md @@ -22,7 +22,7 @@ Here's the available syscalls: #### get_block_hash -```rust +```cairo fn get_block_hash_syscall(block_number: u64) -> SyscallResult ``` @@ -32,14 +32,14 @@ Only within the range `[first_v0_12_0_block, current_block - 10]`. #### get_execution_info -```rust +```cairo fn get_execution_info_syscall() -> SyscallResult> ``` Get information about the current execution context. The returned `ExecutionInfo` is defined as : -```rust +```cairo #[derive(Copy, Drop, Debug)] pub struct ExecutionInfo { pub block_info: Box, @@ -103,7 +103,7 @@ pub struct TxInfo { #### call_contract -```rust +```cairo fn call_contract_syscall( address: ContractAddress, entry_point_selector: felt252, calldata: Span ) -> SyscallResult> @@ -118,7 +118,7 @@ This is not the recommended way to call a contract. Instead, use the dispatcher #### deploy -```rust +```cairo fn deploy_syscall( class_hash: ClassHash, contract_address_salt: felt252, @@ -134,13 +134,13 @@ The success result is a tuple containing the deployed contract address and the r Example of the usage of the `deploy` syscall from the [Factory pattern](../interacting/factory.md): -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/factory/src/simple_factory.cairo:deploy}} ``` #### emit_event -```rust +```cairo fn emit_event_syscall( keys: Span, data: Span ) -> SyscallResult<()> @@ -150,7 +150,7 @@ Emit an event with the given `keys` and `data`. Example of the usage of the `emit_event` syscall from the [Events](../basics/events.md) chapter: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/events/src/counter.cairo:emit}} ``` @@ -168,7 +168,7 @@ emit_event_syscall(keys, values).unwrap_syscall(); #### library_call -```rust +```cairo fn library_call_syscall( class_hash: ClassHash, function_selector: felt252, calldata: Span ) -> SyscallResult> @@ -181,7 +181,7 @@ This is analogous to a delegate call in Ethereum, but only a single class is cal #### send_message_to_L1 -```rust +```cairo fn send_message_to_l1_syscall( to_address: felt252, payload: Span ) -> SyscallResult<()> @@ -193,7 +193,7 @@ Send a message to the L1 contract at `to_address` with the given `payload`. #### replace_class -```rust +```cairo fn replace_class_syscall( class_hash: ClassHash ) -> SyscallResult<()> @@ -203,7 +203,7 @@ Replace the class of the calling contract with the class `class_hash`. This is used for contract upgrades. Here's an example from the [Upgradeable Contract](../../applications/upgradeable_contract.md): -```rust +```cairo {{#rustdoc_include ../../../listings/applications/upgradeable_contract/src/upgradeable_contract_v0.cairo:upgrade}} ``` @@ -212,7 +212,7 @@ The current transaction containing the `replace_class` syscall will continue to #### storage_read -```rust +```cairo fn storage_read_syscall( address_domain: u32, address: StorageAddress, ) -> SyscallResult @@ -225,7 +225,7 @@ Currently, only mode `ONCHAIN` (`0`) is supported. #### storage_write -```rust +```cairo fn storage_write_syscall( address_domain: u32, address: StorageAddress, value: felt252 ) -> SyscallResult<()> @@ -243,7 +243,7 @@ You can also read the [official documentation page](https://docs.starknet.io/doc #### Gas cost -```rust +```cairo mod gas_costs { const STEP: usize = 100; const RANGE_CHECK: usize = 70; diff --git a/src/getting-started/basics/variables.md b/src/getting-started/basics/variables.md index ff9a785f..411f4a53 100644 --- a/src/getting-started/basics/variables.md +++ b/src/getting-started/basics/variables.md @@ -20,7 +20,7 @@ Local variables are stored in memory and are not stored on the blockchain. This Here's a simple example of a contract with only local variables: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/variables/src/local_variables.cairo:contract}} ``` @@ -34,7 +34,7 @@ On the other hand, you can read state variables for free, without any transactio Here's a simple example of a contract with one storage variable: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/variables/src/storage_variables.cairo:contract}} ``` @@ -46,6 +46,6 @@ In Starknet, you can access global variables by using specific functions from th For example, the `get_caller_address` function returns the address of the caller of the current transaction, and the `get_contract_address` function returns the address of the current contract. -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/variables/src/global_variables.cairo:contract}} ``` diff --git a/src/getting-started/basics/visibility-mutability.md b/src/getting-started/basics/visibility-mutability.md index c7ec4301..7da3c801 100644 --- a/src/getting-started/basics/visibility-mutability.md +++ b/src/getting-started/basics/visibility-mutability.md @@ -24,6 +24,6 @@ Internal functions can't be called externally, but the same principle applies re Let's take a look at a simple example contract to see these in action: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/visibility/src/visibility.cairo:contract}} ``` diff --git a/src/getting-started/cairo_cheatsheet/arrays.md b/src/getting-started/cairo_cheatsheet/arrays.md index d61f4141..036af262 100644 --- a/src/getting-started/cairo_cheatsheet/arrays.md +++ b/src/getting-started/cairo_cheatsheet/arrays.md @@ -3,7 +3,7 @@ Arrays are collections of elements of the same type. The possible operations on arrays are defined with the `array::ArrayTrait` of the corelib: -```rust +```cairo trait ArrayTrait { fn new() -> Array; fn append(ref self: Array, value: T); @@ -19,6 +19,6 @@ trait ArrayTrait { For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/array_example.cairo}} ``` diff --git a/src/getting-started/cairo_cheatsheet/dict.md b/src/getting-started/cairo_cheatsheet/dict.md index eaa654ae..a65944bb 100644 --- a/src/getting-started/cairo_cheatsheet/dict.md +++ b/src/getting-started/cairo_cheatsheet/dict.md @@ -7,6 +7,6 @@ A dictionary is a data structure used to store key-value pairs, enabling efficie For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/dict_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/enums.md b/src/getting-started/cairo_cheatsheet/enums.md index c172f689..2d39e3dc 100644 --- a/src/getting-started/cairo_cheatsheet/enums.md +++ b/src/getting-started/cairo_cheatsheet/enums.md @@ -4,7 +4,7 @@ Just like other programming languages, enums (enumerations) are used in cairo to In cairo, `enum variants` can hold different data types (the unit type, structs, other enums, tuples, default core library types, arrays, dictionaries, ...), as shown in the code snippet below. Furthermore, as a quick reminder, enums are expressions, meaning they can return values. -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/enum_example.cairo:enums}} ``` @@ -22,6 +22,6 @@ Enums can be declared both inside and outside a contract. If declared outside, t Here is an example of a contract illustrating the above statements : -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/enum_example.cairo:enum_contract}} ``` diff --git a/src/getting-started/cairo_cheatsheet/felt.md b/src/getting-started/cairo_cheatsheet/felt.md index 6f1bd841..31d49192 100644 --- a/src/getting-started/cairo_cheatsheet/felt.md +++ b/src/getting-started/cairo_cheatsheet/felt.md @@ -5,6 +5,6 @@ For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/felt_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/if_let.md b/src/getting-started/cairo_cheatsheet/if_let.md index 042456db..9110b125 100644 --- a/src/getting-started/cairo_cheatsheet/if_let.md +++ b/src/getting-started/cairo_cheatsheet/if_let.md @@ -2,7 +2,7 @@ A `if let` statement is a combination of an `if` statement and a `let` statement. It allows you to execute the block only if the pattern matches. It's a cleaner way to handle a `match` statement with only one pattern that you want to handle. -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/if_let_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/loop.md b/src/getting-started/cairo_cheatsheet/loop.md index cdaf96ff..7411e8b7 100644 --- a/src/getting-started/cairo_cheatsheet/loop.md +++ b/src/getting-started/cairo_cheatsheet/loop.md @@ -4,7 +4,7 @@ A `loop` specifies a block of code that will run repetitively until a halting co For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/loop_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/mapping.md b/src/getting-started/cairo_cheatsheet/mapping.md index fee30ec6..f32d44b5 100644 --- a/src/getting-started/cairo_cheatsheet/mapping.md +++ b/src/getting-started/cairo_cheatsheet/mapping.md @@ -2,6 +2,6 @@ The `Map` type can be used to represent a collection of key-value. -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/mapping_example.cairo}} ``` diff --git a/src/getting-started/cairo_cheatsheet/match.md b/src/getting-started/cairo_cheatsheet/match.md index d5a7b2f4..d74411ee 100644 --- a/src/getting-started/cairo_cheatsheet/match.md +++ b/src/getting-started/cairo_cheatsheet/match.md @@ -4,6 +4,6 @@ The `match` expression in Cairo allows us to control the flow of our code by com For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/match_example.cairo}} ``` diff --git a/src/getting-started/cairo_cheatsheet/struct.md b/src/getting-started/cairo_cheatsheet/struct.md index 768c813a..9200d255 100644 --- a/src/getting-started/cairo_cheatsheet/struct.md +++ b/src/getting-started/cairo_cheatsheet/struct.md @@ -4,6 +4,6 @@ A struct is a data type similar to a tuple. Like tuples, they can be used to hol For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/struct_example.cairo}} ``` diff --git a/src/getting-started/cairo_cheatsheet/tuples.md b/src/getting-started/cairo_cheatsheet/tuples.md index b3218b67..b81bcabc 100644 --- a/src/getting-started/cairo_cheatsheet/tuples.md +++ b/src/getting-started/cairo_cheatsheet/tuples.md @@ -4,6 +4,6 @@ Tuples is a data type to group a fixed number of items of potentially different For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/tuple_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/type_casting.md b/src/getting-started/cairo_cheatsheet/type_casting.md index 7e78d891..28048779 100644 --- a/src/getting-started/cairo_cheatsheet/type_casting.md +++ b/src/getting-started/cairo_cheatsheet/type_casting.md @@ -5,6 +5,6 @@ The `into` method is used for conversion from a smaller data type to a larger da For example: -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/type_casting_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/while.md b/src/getting-started/cairo_cheatsheet/while.md index 51c91602..420a648b 100644 --- a/src/getting-started/cairo_cheatsheet/while.md +++ b/src/getting-started/cairo_cheatsheet/while.md @@ -2,7 +2,7 @@ A `while` loop allows you to specify a condition that must be true for the loop to continue. -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/while_example.cairo:sheet}} ``` diff --git a/src/getting-started/cairo_cheatsheet/while_let.md b/src/getting-started/cairo_cheatsheet/while_let.md index 90eda4b3..fbad1232 100644 --- a/src/getting-started/cairo_cheatsheet/while_let.md +++ b/src/getting-started/cairo_cheatsheet/while_let.md @@ -2,7 +2,7 @@ A `while let` loop is a combination of a `while` loop and a `let` statement. It allows you to execute the loop body only if the pattern matches. -```rust +```cairo {{#include ../../../listings/getting-started/cairo_cheatsheet/src/while_let_example.cairo:sheet}} ``` diff --git a/src/getting-started/interacting/calling_other_contracts.md b/src/getting-started/interacting/calling_other_contracts.md index 027a3a5b..72551a44 100644 --- a/src/getting-started/interacting/calling_other_contracts.md +++ b/src/getting-started/interacting/calling_other_contracts.md @@ -11,12 +11,12 @@ In order to call other contracts using dispatchers, you will need to define the Here's the `Callee` contract interface and implementation: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/calling_other_contracts/src/caller.cairo:callee_contract}} ``` The following `Caller` contract uses the `Callee` dispatcher to call the `Callee` contract: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/calling_other_contracts/src/caller.cairo:caller_contract}} ``` diff --git a/src/getting-started/interacting/factory.md b/src/getting-started/interacting/factory.md index d8b06870..64dd47e0 100644 --- a/src/getting-started/interacting/factory.md +++ b/src/getting-started/interacting/factory.md @@ -16,7 +16,7 @@ Using the factory pattern, we can deploy multiple instances of the same contract Here's a minimal example of a factory contract that deploys the `SimpleCounter` contract: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/factory/src/simple_factory.cairo:contract}} ``` diff --git a/src/getting-started/interacting/interfaces-traits.md b/src/getting-started/interacting/interfaces-traits.md index 4c8ed663..dc69c225 100644 --- a/src/getting-started/interacting/interfaces-traits.md +++ b/src/getting-started/interacting/interfaces-traits.md @@ -15,13 +15,13 @@ In summary, there's two ways to handle interfaces: ## Explicit interface -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/interfaces_traits/src/explicit.cairo:contract}} ``` ## Implicit interface -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/interfaces_traits/src/implicit.cairo:contract}} ``` @@ -32,6 +32,6 @@ In summary, there's two ways to handle interfaces: You can also use `#[generate_trait]` for your internal functions. Since this trait is generated in the context of the contract, you can define pure functions as well (functions without the `self` parameter). -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/interfaces_traits/src/implicit_internal.cairo:contract}} ``` diff --git a/src/getting-started/testing/contract-testing.md b/src/getting-started/testing/contract-testing.md index ae51404a..51d73937 100644 --- a/src/getting-started/testing/contract-testing.md +++ b/src/getting-started/testing/contract-testing.md @@ -4,13 +4,13 @@ Testing plays a crucial role in software development, especially for smart contr Let's start with a simple smart contract as an example: -```rust +```cairo {{#include ../../../listings/getting-started/testing_how_to/src/contract.cairo:contract}} ``` Now, take a look at the tests for this contract: -```rust +```cairo {{#include ../../../listings/getting-started/testing_how_to/src/contract.cairo:tests}} ``` @@ -39,7 +39,7 @@ This can be useful for testing internal functions, or specific state mutations t Here is an example of how to do the same previous test using the contract state: -```rust +```cairo {{#include ../../../listings/getting-started/testing_how_to/src/contract.cairo:tests_with_state}} ``` @@ -49,7 +49,7 @@ In order to test events, you need to use the `starknet::pop_log` function. If th See the test for the [Events](../events.md) section: -```rust +```cairo {{#rustdoc_include ../../../listings/getting-started/events/src/counter.cairo:test_events}} ``` diff --git a/theme/book.js b/theme/book.js index a7b43b0e..8b8004ff 100644 --- a/theme/book.js +++ b/theme/book.js @@ -1,714 +1,792 @@ "use strict"; // Fix back button cache problem -window.onunload = function () { }; +window.onunload = function () {}; // Global variable, shared between modules function playground_text(playground, hidden = true) { - let code_block = playground.querySelector("code"); - - if (window.ace && code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - return editor.getValue(); - } else if (hidden) { - return code_block.textContent; - } else { - return code_block.innerText; - } + let code_block = playground.querySelector("code"); + + if (window.ace && code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + return editor.getValue(); + } else if (hidden) { + return code_block.textContent; + } else { + return code_block.innerText; + } } (function codeSnippets() { - function fetch_with_timeout(url, options, timeout = 6000) { - return Promise.race([ - fetch(url, options), - new Promise((_, reject) => setTimeout(() => reject(new Error('timeout')), timeout)) - ]); - } - - var playgrounds = Array.from(document.querySelectorAll(".playground")); - if (playgrounds.length > 0) { - fetch_with_timeout("https://play.rust-lang.org/meta/crates", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - }) - .then(response => response.json()) - .then(response => { - // get list of crates available in the rust playground - let playground_crates = response.crates.map(item => item["id"]); - playgrounds.forEach(block => handle_crate_list_update(block, playground_crates)); + function fetch_with_timeout(url, options, timeout = 6000) { + return Promise.race([ + fetch(url, options), + new Promise((_, reject) => + setTimeout(() => reject(new Error("timeout")), timeout) + ), + ]); + } + + var playgrounds = Array.from(document.querySelectorAll(".playground")); + if (playgrounds.length > 0) { + fetch_with_timeout("https://play.rust-lang.org/meta/crates", { + headers: { + "Content-Type": "application/json", + }, + method: "POST", + mode: "cors", + }) + .then((response) => response.json()) + .then((response) => { + // get list of crates available in the rust playground + let playground_crates = response.crates.map((item) => item["id"]); + playgrounds.forEach((block) => + handle_crate_list_update(block, playground_crates) + ); + }); + } + + function handle_crate_list_update(playground_block, playground_crates) { + // update the play buttons after receiving the response + update_play_button(playground_block, playground_crates); + + // and install on change listener to dynamically update ACE editors + if (window.ace) { + let code_block = playground_block.querySelector("code"); + if (code_block.classList.contains("editable")) { + let editor = window.ace.edit(code_block); + editor.addEventListener("change", function (e) { + update_play_button(playground_block, playground_crates); }); + // add Ctrl-Enter command to execute rust code + editor.commands.addCommand({ + name: "run", + bindKey: { + win: "Ctrl-Enter", + mac: "Ctrl-Enter", + }, + exec: (_editor) => run_rust_code(playground_block), + }); + } } + } - function handle_crate_list_update(playground_block, playground_crates) { - // update the play buttons after receiving the response - update_play_button(playground_block, playground_crates); - - // and install on change listener to dynamically update ACE editors - if (window.ace) { - let code_block = playground_block.querySelector("code"); - if (code_block.classList.contains("editable")) { - let editor = window.ace.edit(code_block); - editor.addEventListener("change", function (e) { - update_play_button(playground_block, playground_crates); - }); - // add Ctrl-Enter command to execute rust code - editor.commands.addCommand({ - name: "run", - bindKey: { - win: "Ctrl-Enter", - mac: "Ctrl-Enter" - }, - exec: _editor => run_rust_code(playground_block) - }); - } - } - } - - // updates the visibility of play button based on `no_run` class and - // used crates vs ones available on https://play.rust-lang.org - function update_play_button(pre_block, playground_crates) { - var play_button = pre_block.querySelector(".play-button"); + // updates the visibility of play button based on `no_run` class and + // used crates vs ones available on https://play.rust-lang.org + function update_play_button(pre_block, playground_crates) { + var play_button = pre_block.querySelector(".play-button"); - // skip if code is `no_run` - if (pre_block.querySelector('code').classList.contains("no_run")) { - play_button.classList.add("hidden"); - return; - } + // skip if code is `no_run` + if (pre_block.querySelector("code").classList.contains("no_run")) { + play_button.classList.add("hidden"); + return; + } - // get list of `extern crate`'s from snippet - var txt = playground_text(pre_block); - var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; - var snippet_crates = []; - var item; - while (item = re.exec(txt)) { - snippet_crates.push(item[1]); - } + // get list of `extern crate`'s from snippet + var txt = playground_text(pre_block); + var re = /extern\s+crate\s+([a-zA-Z_0-9]+)\s*;/g; + var snippet_crates = []; + var item; + while ((item = re.exec(txt))) { + snippet_crates.push(item[1]); + } - // check if all used crates are available on play.rust-lang.org - var all_available = snippet_crates.every(function (elem) { - return playground_crates.indexOf(elem) > -1; - }); + // check if all used crates are available on play.rust-lang.org + var all_available = snippet_crates.every(function (elem) { + return playground_crates.indexOf(elem) > -1; + }); - if (all_available) { - play_button.classList.remove("hidden"); - } else { - play_button.classList.add("hidden"); - } + if (all_available) { + play_button.classList.remove("hidden"); + } else { + play_button.classList.add("hidden"); } + } - function run_rust_code(code_block) { - var result_block = code_block.querySelector(".result"); - if (!result_block) { - result_block = document.createElement('code'); - result_block.className = 'result hljs language-bash'; + function run_rust_code(code_block) { + var result_block = code_block.querySelector(".result"); + if (!result_block) { + result_block = document.createElement("code"); + result_block.className = "result hljs language-bash"; - code_block.append(result_block); - } + code_block.append(result_block); + } - let text = playground_text(code_block); - let classes = code_block.querySelector('code').classList; - let edition = "2015"; - if(classes.contains("edition2018")) { - edition = "2018"; - } else if(classes.contains("edition2021")) { - edition = "2021"; - } - var params = { - version: "stable", - optimize: "0", - code: text, - edition: edition - }; - - if (text.indexOf("#![feature") !== -1) { - params.version = "nightly"; - } + let text = playground_text(code_block); + let classes = code_block.querySelector("code").classList; + let edition = "2015"; + if (classes.contains("edition2018")) { + edition = "2018"; + } else if (classes.contains("edition2021")) { + edition = "2021"; + } + var params = { + version: "stable", + optimize: "0", + code: text, + edition: edition, + }; - result_block.innerText = "Running..."; - - fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { - headers: { - 'Content-Type': "application/json", - }, - method: 'POST', - mode: 'cors', - body: JSON.stringify(params) - }) - .then(response => response.json()) - .then(response => { - if (response.result.trim() === '') { - result_block.innerText = "No output"; - result_block.classList.add("result-no-output"); - } else { - result_block.innerText = response.result; - result_block.classList.remove("result-no-output"); - } - }) - .catch(error => result_block.innerText = "Playground Communication: " + error.message); + if (text.indexOf("#![feature") !== -1) { + params.version = "nightly"; } - // Syntax highlighting Configuration - hljs.configure({ - tabReplace: ' ', // 4 spaces - languages: [], // Languages used for auto-detection + result_block.innerText = "Running..."; + + fetch_with_timeout("https://play.rust-lang.org/evaluate.json", { + headers: { + "Content-Type": "application/json", + }, + method: "POST", + mode: "cors", + body: JSON.stringify(params), + }) + .then((response) => response.json()) + .then((response) => { + if (response.result.trim() === "") { + result_block.innerText = "No output"; + result_block.classList.add("result-no-output"); + } else { + result_block.innerText = response.result; + result_block.classList.remove("result-no-output"); + } + }) + .catch( + (error) => + (result_block.innerText = + "Playground Communication: " + error.message) + ); + } + + // Syntax highlighting Configuration + hljs.configure({ + tabReplace: " ", // 4 spaces + languages: [], // Languages used for auto-detection + }); + + let code_nodes = Array.from(document.querySelectorAll("code")) + // Don't highlight `inline code` blocks in headers. + .filter(function (node) { + return !node.parentElement.classList.contains("header"); }); - let code_nodes = Array - .from(document.querySelectorAll('code')) - // Don't highlight `inline code` blocks in headers. - .filter(function (node) {return !node.parentElement.classList.contains("header"); }); - - if (window.ace) { - // language-rust class needs to be removed for editable - // blocks or highlightjs will capture events - code_nodes - .filter(function (node) {return node.classList.contains("editable"); }) - .forEach(function (block) { block.classList.remove('language-rust'); }); - - code_nodes - .filter(function (node) {return !node.classList.contains("editable"); }) - .forEach(function (block) { hljs.highlightBlock(block); }); - } else { - code_nodes.forEach(function (block) { hljs.highlightBlock(block); }); + if (window.ace) { + // language-cairo class needs to be removed for editable + // blocks or highlightjs will capture events + code_nodes + .filter(function (node) { + return node.classList.contains("editable"); + }) + .forEach(function (block) { + block.classList.remove("language-cairo"); + }); + + code_nodes + .filter(function (node) { + return !node.classList.contains("editable"); + }) + .forEach(function (block) { + hljs.highlightBlock(block); + }); + } else { + code_nodes.forEach(function (block) { + hljs.highlightBlock(block); + }); + } + + // Adding the hljs class gives code blocks the color css + // even if highlighting doesn't apply + code_nodes.forEach(function (block) { + block.classList.add("hljs"); + }); + + Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) { + var lines = Array.from(block.querySelectorAll(".boring")); + // If no lines were hidden, return + if (!lines.length) { + return; } + block.classList.add("hide-boring"); + + var buttons = document.createElement("div"); + buttons.className = "buttons"; + buttons.innerHTML = + ''; + + // add expand button + var pre_block = block.parentNode; + pre_block.insertBefore(buttons, pre_block.firstChild); + + pre_block.querySelector(".buttons").addEventListener("click", function (e) { + if (e.target.classList.contains("fa-eye")) { + e.target.classList.remove("fa-eye"); + e.target.classList.add("fa-eye-slash"); + e.target.title = "Hide lines"; + e.target.setAttribute("aria-label", e.target.title); + + block.classList.remove("hide-boring"); + } else if (e.target.classList.contains("fa-eye-slash")) { + e.target.classList.remove("fa-eye-slash"); + e.target.classList.add("fa-eye"); + e.target.title = "Show hidden lines"; + e.target.setAttribute("aria-label", e.target.title); - // Adding the hljs class gives code blocks the color css - // even if highlighting doesn't apply - code_nodes.forEach(function (block) { block.classList.add('hljs'); }); - - Array.from(document.querySelectorAll("code.hljs")).forEach(function (block) { - - var lines = Array.from(block.querySelectorAll('.boring')); - // If no lines were hidden, return - if (!lines.length) { return; } block.classList.add("hide-boring"); - - var buttons = document.createElement('div'); - buttons.className = 'buttons'; - buttons.innerHTML = ""; - - // add expand button - var pre_block = block.parentNode; - pre_block.insertBefore(buttons, pre_block.firstChild); - - pre_block.querySelector('.buttons').addEventListener('click', function (e) { - if (e.target.classList.contains('fa-eye')) { - e.target.classList.remove('fa-eye'); - e.target.classList.add('fa-eye-slash'); - e.target.title = 'Hide lines'; - e.target.setAttribute('aria-label', e.target.title); - - block.classList.remove('hide-boring'); - } else if (e.target.classList.contains('fa-eye-slash')) { - e.target.classList.remove('fa-eye-slash'); - e.target.classList.add('fa-eye'); - e.target.title = 'Show hidden lines'; - e.target.setAttribute('aria-label', e.target.title); - - block.classList.add('hide-boring'); - } - }); + } }); + }); - if (window.playground_copyable) { - Array.from(document.querySelectorAll('pre code')).forEach(function (block) { - var pre_block = block.parentNode; - if (!pre_block.classList.contains('playground')) { - var buttons = pre_block.querySelector(".buttons"); - if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); - } - - var clipButton = document.createElement('button'); - clipButton.className = 'fa fa-copy clip-button'; - clipButton.title = 'Copy to clipboard'; - clipButton.setAttribute('aria-label', clipButton.title); - clipButton.innerHTML = ''; - - buttons.insertBefore(clipButton, buttons.firstChild); - } - }); - } - - // Process playground code blocks - Array.from(document.querySelectorAll(".playground")).forEach(function (pre_block) { - // Add play button + if (window.playground_copyable) { + Array.from(document.querySelectorAll("pre code")).forEach(function (block) { + var pre_block = block.parentNode; + if (!pre_block.classList.contains("playground")) { var buttons = pre_block.querySelector(".buttons"); if (!buttons) { - buttons = document.createElement('div'); - buttons.className = 'buttons'; - pre_block.insertBefore(buttons, pre_block.firstChild); + buttons = document.createElement("div"); + buttons.className = "buttons"; + pre_block.insertBefore(buttons, pre_block.firstChild); } - var runCodeButton = document.createElement('button'); - runCodeButton.className = 'fa fa-play play-button'; - runCodeButton.hidden = true; - runCodeButton.title = 'Run this code'; - runCodeButton.setAttribute('aria-label', runCodeButton.title); + var clipButton = document.createElement("button"); + clipButton.className = "fa fa-copy clip-button"; + clipButton.title = "Copy to clipboard"; + clipButton.setAttribute("aria-label", clipButton.title); + clipButton.innerHTML = ''; - buttons.insertBefore(runCodeButton, buttons.firstChild); - runCodeButton.addEventListener('click', function (e) { - run_rust_code(pre_block); - }); + buttons.insertBefore(clipButton, buttons.firstChild); + } + }); + } + + // Process playground code blocks + Array.from(document.querySelectorAll(".playground")).forEach(function ( + pre_block + ) { + // Add play button + var buttons = pre_block.querySelector(".buttons"); + if (!buttons) { + buttons = document.createElement("div"); + buttons.className = "buttons"; + pre_block.insertBefore(buttons, pre_block.firstChild); + } - if (window.playground_copyable) { - var copyCodeClipboardButton = document.createElement('button'); - copyCodeClipboardButton.className = 'fa fa-copy clip-button'; - copyCodeClipboardButton.innerHTML = ''; - copyCodeClipboardButton.title = 'Copy to clipboard'; - copyCodeClipboardButton.setAttribute('aria-label', copyCodeClipboardButton.title); + var runCodeButton = document.createElement("button"); + runCodeButton.className = "fa fa-play play-button"; + runCodeButton.hidden = true; + runCodeButton.title = "Run this code"; + runCodeButton.setAttribute("aria-label", runCodeButton.title); - buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); - } + buttons.insertBefore(runCodeButton, buttons.firstChild); + runCodeButton.addEventListener("click", function (e) { + run_rust_code(pre_block); + }); + + if (window.playground_copyable) { + var copyCodeClipboardButton = document.createElement("button"); + copyCodeClipboardButton.className = "fa fa-copy clip-button"; + copyCodeClipboardButton.innerHTML = ''; + copyCodeClipboardButton.title = "Copy to clipboard"; + copyCodeClipboardButton.setAttribute( + "aria-label", + copyCodeClipboardButton.title + ); + + buttons.insertBefore(copyCodeClipboardButton, buttons.firstChild); + } - let code_block = pre_block.querySelector("code"); - if (window.ace && code_block.classList.contains("editable")) { - var undoChangesButton = document.createElement('button'); - undoChangesButton.className = 'fa fa-history reset-button'; - undoChangesButton.title = 'Undo changes'; - undoChangesButton.setAttribute('aria-label', undoChangesButton.title); + let code_block = pre_block.querySelector("code"); + if (window.ace && code_block.classList.contains("editable")) { + var undoChangesButton = document.createElement("button"); + undoChangesButton.className = "fa fa-history reset-button"; + undoChangesButton.title = "Undo changes"; + undoChangesButton.setAttribute("aria-label", undoChangesButton.title); - buttons.insertBefore(undoChangesButton, buttons.firstChild); + buttons.insertBefore(undoChangesButton, buttons.firstChild); - undoChangesButton.addEventListener('click', function () { - let editor = window.ace.edit(code_block); - editor.setValue(editor.originalCode); - editor.clearSelection(); - }); - } - }); + undoChangesButton.addEventListener("click", function () { + let editor = window.ace.edit(code_block); + editor.setValue(editor.originalCode); + editor.clearSelection(); + }); + } + }); })(); (function themes() { - var html = document.querySelector('html'); - var themeToggleButton = document.getElementById('theme-toggle'); - var themePopup = document.getElementById('theme-list'); - var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); - var stylesheets = { - ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), - tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), - highlight: document.querySelector("[href$='highlight.css']"), - }; - - function showThemes() { - themePopup.style.display = 'block'; - themeToggleButton.setAttribute('aria-expanded', true); - themePopup.querySelector("button#" + get_theme()).focus(); + var html = document.querySelector("html"); + var themeToggleButton = document.getElementById("theme-toggle"); + var themePopup = document.getElementById("theme-list"); + var themeColorMetaTag = document.querySelector('meta[name="theme-color"]'); + var stylesheets = { + ayuHighlight: document.querySelector("[href$='ayu-highlight.css']"), + tomorrowNight: document.querySelector("[href$='tomorrow-night.css']"), + highlight: document.querySelector("[href$='highlight.css']"), + }; + + function showThemes() { + themePopup.style.display = "block"; + themeToggleButton.setAttribute("aria-expanded", true); + themePopup.querySelector("button#" + get_theme()).focus(); + } + + function updateThemeSelected() { + themePopup.querySelectorAll(".theme-selected").forEach(function (el) { + el.classList.remove("theme-selected"); + }); + themePopup + .querySelector("button#" + get_theme()) + .classList.add("theme-selected"); + } + + function hideThemes() { + themePopup.style.display = "none"; + themeToggleButton.setAttribute("aria-expanded", false); + themeToggleButton.focus(); + } + + function get_theme() { + var theme; + try { + theme = localStorage.getItem("mdbook-theme"); + } catch (e) {} + if (theme === null || theme === undefined) { + return default_theme; + } else { + return theme; } - - function updateThemeSelected() { - themePopup.querySelectorAll('.theme-selected').forEach(function (el) { - el.classList.remove('theme-selected'); - }); - themePopup.querySelector("button#" + get_theme()).classList.add('theme-selected'); + } + + function set_theme(theme, store = true) { + let ace_theme; + + if (theme == "coal" || theme == "navy") { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = false; + stylesheets.highlight.disabled = true; + + ace_theme = "ace/theme/tomorrow_night"; + } else if (theme == "ayu") { + stylesheets.ayuHighlight.disabled = false; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = true; + ace_theme = "ace/theme/tomorrow_night"; + } else { + stylesheets.ayuHighlight.disabled = true; + stylesheets.tomorrowNight.disabled = true; + stylesheets.highlight.disabled = false; + ace_theme = "ace/theme/dawn"; } - function hideThemes() { - themePopup.style.display = 'none'; - themeToggleButton.setAttribute('aria-expanded', false); - themeToggleButton.focus(); - } + setTimeout(function () { + themeColorMetaTag.content = getComputedStyle( + document.documentElement + ).backgroundColor; + }, 1); - function get_theme() { - var theme; - try { theme = localStorage.getItem('mdbook-theme'); } catch (e) { } - if (theme === null || theme === undefined) { - return default_theme; - } else { - return theme; - } + if (window.ace && window.editors) { + window.editors.forEach(function (editor) { + editor.setTheme(ace_theme); + }); } - function set_theme(theme, store = true) { - let ace_theme; - - if (theme == 'coal' || theme == 'navy') { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = false; - stylesheets.highlight.disabled = true; - - ace_theme = "ace/theme/tomorrow_night"; - } else if (theme == 'ayu') { - stylesheets.ayuHighlight.disabled = false; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = true; - ace_theme = "ace/theme/tomorrow_night"; - } else { - stylesheets.ayuHighlight.disabled = true; - stylesheets.tomorrowNight.disabled = true; - stylesheets.highlight.disabled = false; - ace_theme = "ace/theme/dawn"; - } + var previousTheme = get_theme(); - setTimeout(function () { - themeColorMetaTag.content = getComputedStyle(document.documentElement).backgroundColor; - }, 1); + if (store) { + try { + localStorage.setItem("mdbook-theme", theme); + } catch (e) {} + } - if (window.ace && window.editors) { - window.editors.forEach(function (editor) { - editor.setTheme(ace_theme); - }); - } + html.classList.remove(previousTheme); + html.classList.add(theme); + updateThemeSelected(); + } - var previousTheme = get_theme(); + // Set theme + var theme = get_theme(); - if (store) { - try { localStorage.setItem('mdbook-theme', theme); } catch (e) { } - } + set_theme(theme, false); - html.classList.remove(previousTheme); - html.classList.add(theme); - updateThemeSelected(); + themeToggleButton.addEventListener("click", function () { + if (themePopup.style.display === "block") { + hideThemes(); + } else { + showThemes(); } + }); + + themePopup.addEventListener("click", function (e) { + var theme; + if (e.target.className === "theme") { + theme = e.target.id; + } else if (e.target.parentElement.className === "theme") { + theme = e.target.parentElement.id; + } else { + return; + } + set_theme(theme); + }); + + themePopup.addEventListener("focusout", function (e) { + // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) + if ( + !!e.relatedTarget && + !themeToggleButton.contains(e.relatedTarget) && + !themePopup.contains(e.relatedTarget) + ) { + hideThemes(); + } + }); + + // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 + document.addEventListener("click", function (e) { + if ( + themePopup.style.display === "block" && + !themeToggleButton.contains(e.target) && + !themePopup.contains(e.target) + ) { + hideThemes(); + } + }); - // Set theme - var theme = get_theme(); - - set_theme(theme, false); - - themeToggleButton.addEventListener('click', function () { - if (themePopup.style.display === 'block') { - hideThemes(); - } else { - showThemes(); - } - }); + document.addEventListener("keydown", function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return; + } + if (!themePopup.contains(e.target)) { + return; + } - themePopup.addEventListener('click', function (e) { - var theme; - if (e.target.className === "theme") { - theme = e.target.id; - } else if (e.target.parentElement.className === "theme") { - theme = e.target.parentElement.id; - } else { - return; + switch (e.key) { + case "Escape": + e.preventDefault(); + hideThemes(); + break; + case "ArrowUp": + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.previousElementSibling) { + li.previousElementSibling.querySelector("button").focus(); } - set_theme(theme); - }); - - themePopup.addEventListener('focusout', function(e) { - // e.relatedTarget is null in Safari and Firefox on macOS (see workaround below) - if (!!e.relatedTarget && !themeToggleButton.contains(e.relatedTarget) && !themePopup.contains(e.relatedTarget)) { - hideThemes(); + break; + case "ArrowDown": + e.preventDefault(); + var li = document.activeElement.parentElement; + if (li && li.nextElementSibling) { + li.nextElementSibling.querySelector("button").focus(); } - }); + break; + case "Home": + e.preventDefault(); + themePopup.querySelector("li:first-child button").focus(); + break; + case "End": + e.preventDefault(); + themePopup.querySelector("li:last-child button").focus(); + break; + } + }); +})(); - // Should not be needed, but it works around an issue on macOS & iOS: https://github.com/rust-lang/mdBook/issues/628 - document.addEventListener('click', function(e) { - if (themePopup.style.display === 'block' && !themeToggleButton.contains(e.target) && !themePopup.contains(e.target)) { - hideThemes(); - } +(function sidebar() { + var body = document.querySelector("body"); + var sidebar = document.getElementById("sidebar"); + var sidebarLinks = document.querySelectorAll("#sidebar a"); + var sidebarToggleButton = document.getElementById("sidebar-toggle"); + var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); + var firstContact = null; + + function showSidebar() { + body.classList.remove("sidebar-hidden"); + body.classList.add("sidebar-visible"); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute("tabIndex", 0); }); - - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (!themePopup.contains(e.target)) { return; } - - switch (e.key) { - case 'Escape': - e.preventDefault(); - hideThemes(); - break; - case 'ArrowUp': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.previousElementSibling) { - li.previousElementSibling.querySelector('button').focus(); - } - break; - case 'ArrowDown': - e.preventDefault(); - var li = document.activeElement.parentElement; - if (li && li.nextElementSibling) { - li.nextElementSibling.querySelector('button').focus(); - } - break; - case 'Home': - e.preventDefault(); - themePopup.querySelector('li:first-child button').focus(); - break; - case 'End': - e.preventDefault(); - themePopup.querySelector('li:last-child button').focus(); - break; + sidebarToggleButton.setAttribute("aria-expanded", true); + sidebar.setAttribute("aria-hidden", false); + try { + localStorage.setItem("mdbook-sidebar", "visible"); + } catch (e) {} + } + + var sidebarAnchorToggles = document.querySelectorAll("#sidebar a.toggle"); + + function toggleSection(ev) { + ev.currentTarget.parentElement.classList.toggle("expanded"); + const toggleId = ev.currentTarget.parentElement.getAttribute("toggle-id"); + const isExpanded = + ev.currentTarget.parentElement.classList.contains("expanded"); + try { + localStorage.setItem( + `mdbook-sidebar-toggle-${toggleId}`, + isExpanded ? 1 : 0 + ); + } catch (e) {} + } + + Array.from(sidebarAnchorToggles).forEach(function (el, id) { + el.parentElement.setAttribute("toggle-id", id); + const isActive = + el.parentElement.nextSibling.querySelector(".active") !== null || + el.previousSibling.classList.contains("active"); + if (!isActive) { + try { + const isExpanded = + localStorage.getItem(`mdbook-sidebar-toggle-${id}`) === "1"; + if (!isExpanded) { + el.parentElement.classList.remove("expanded"); } + localStorage.setItem(`mdbook-sidebar-toggle-${id}`, isExpanded ? 1 : 0); + } catch (e) { + el.parentElement.classList.remove("expanded"); + } + } + el.addEventListener("click", toggleSection); + }); + + function hideSidebar() { + body.classList.remove("sidebar-visible"); + body.classList.add("sidebar-hidden"); + Array.from(sidebarLinks).forEach(function (link) { + link.setAttribute("tabIndex", -1); }); + sidebarToggleButton.setAttribute("aria-expanded", false); + sidebar.setAttribute("aria-hidden", true); + try { + localStorage.setItem("mdbook-sidebar", "hidden"); + } catch (e) {} + } + + // Toggle sidebar + sidebarToggleButton.addEventListener("click", function sidebarToggle() { + if (body.classList.contains("sidebar-hidden")) { + var current_width = parseInt( + document.documentElement.style.getPropertyValue("--sidebar-width"), + 10 + ); + if (current_width < 150) { + document.documentElement.style.setProperty("--sidebar-width", "150px"); + } + showSidebar(); + } else if (body.classList.contains("sidebar-visible")) { + hideSidebar(); + } else { + if (getComputedStyle(sidebar)["transform"] === "none") { + hideSidebar(); + } else { + showSidebar(); + } + } + }); + + sidebarResizeHandle.addEventListener("mousedown", initResize, false); + + function initResize(e) { + window.addEventListener("mousemove", resize, false); + window.addEventListener("mouseup", stopResize, false); + body.classList.add("sidebar-resizing"); + } + function resize(e) { + var pos = e.clientX - sidebar.offsetLeft; + if (pos < 20) { + hideSidebar(); + } else { + if (body.classList.contains("sidebar-hidden")) { + showSidebar(); + } + pos = Math.min(pos, window.innerWidth - 100); + document.documentElement.style.setProperty("--sidebar-width", pos + "px"); + } + } + //on mouseup remove windows functions mousemove & mouseup + function stopResize(e) { + body.classList.remove("sidebar-resizing"); + window.removeEventListener("mousemove", resize, false); + window.removeEventListener("mouseup", stopResize, false); + } + + document.addEventListener( + "touchstart", + function (e) { + firstContact = { + x: e.touches[0].clientX, + time: Date.now(), + }; + }, + { passive: true } + ); + + document.addEventListener( + "touchmove", + function (e) { + if (!firstContact) return; + + var curX = e.touches[0].clientX; + var xDiff = curX - firstContact.x, + tDiff = Date.now() - firstContact.time; + + if (tDiff < 250 && Math.abs(xDiff) >= 150) { + if ( + xDiff >= 0 && + firstContact.x < Math.min(document.body.clientWidth * 0.25, 300) + ) + showSidebar(); + else if (xDiff < 0 && curX < 300) hideSidebar(); + + firstContact = null; + } + }, + { passive: true } + ); })(); -(function sidebar() { - var body = document.querySelector("body"); - var sidebar = document.getElementById("sidebar"); - var sidebarLinks = document.querySelectorAll('#sidebar a'); - var sidebarToggleButton = document.getElementById("sidebar-toggle"); - var sidebarResizeHandle = document.getElementById("sidebar-resize-handle"); - var firstContact = null; - - function showSidebar() { - body.classList.remove('sidebar-hidden') - body.classList.add('sidebar-visible'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', 0); - }); - sidebarToggleButton.setAttribute('aria-expanded', true); - sidebar.setAttribute('aria-hidden', false); - try { localStorage.setItem('mdbook-sidebar', 'visible'); } catch (e) { } +(function chapterNavigation() { + document.addEventListener("keydown", function (e) { + if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { + return; } - - var sidebarAnchorToggles = document.querySelectorAll('#sidebar a.toggle'); - - function toggleSection(ev) { - ev.currentTarget.parentElement.classList.toggle('expanded'); - const toggleId = ev.currentTarget.parentElement.getAttribute('toggle-id'); - const isExpanded = ev.currentTarget.parentElement.classList.contains('expanded'); - try { - localStorage.setItem(`mdbook-sidebar-toggle-${toggleId}`, isExpanded ? 1 : 0); - } catch (e) {} + if (window.search && window.search.hasFocus()) { + return; } + var html = document.querySelector("html"); - Array.from(sidebarAnchorToggles).forEach(function (el, id) { - el.parentElement.setAttribute('toggle-id', id); - const isActive = el.parentElement.nextSibling.querySelector('.active') !== null || el.previousSibling.classList.contains('active'); - if (!isActive) { - try { - const isExpanded = localStorage.getItem(`mdbook-sidebar-toggle-${id}`) === '1'; - if (!isExpanded) { - el.parentElement.classList.remove('expanded'); - } - localStorage.setItem(`mdbook-sidebar-toggle-${id}`, isExpanded ? 1 : 0); - } catch (e) { - el.parentElement.classList.remove('expanded'); - } + function next() { + var nextButton = document.querySelector(".nav-chapters.next"); + if (nextButton) { + window.location.href = nextButton.href; } - el.addEventListener('click', toggleSection); - }); - - function hideSidebar() { - body.classList.remove('sidebar-visible') - body.classList.add('sidebar-hidden'); - Array.from(sidebarLinks).forEach(function (link) { - link.setAttribute('tabIndex', -1); - }); - sidebarToggleButton.setAttribute('aria-expanded', false); - sidebar.setAttribute('aria-hidden', true); - try { localStorage.setItem('mdbook-sidebar', 'hidden'); } catch (e) { } } - - // Toggle sidebar - sidebarToggleButton.addEventListener('click', function sidebarToggle() { - if (body.classList.contains("sidebar-hidden")) { - var current_width = parseInt( - document.documentElement.style.getPropertyValue('--sidebar-width'), 10); - if (current_width < 150) { - document.documentElement.style.setProperty('--sidebar-width', '150px'); - } - showSidebar(); - } else if (body.classList.contains("sidebar-visible")) { - hideSidebar(); + function prev() { + var previousButton = document.querySelector(".nav-chapters.previous"); + if (previousButton) { + window.location.href = previousButton.href; + } + } + switch (e.key) { + case "ArrowRight": + e.preventDefault(); + if (html.dir == "rtl") { + prev(); } else { - if (getComputedStyle(sidebar)['transform'] === 'none') { - hideSidebar(); - } else { - showSidebar(); - } + next(); } - }); - - sidebarResizeHandle.addEventListener('mousedown', initResize, false); - - function initResize(e) { - window.addEventListener('mousemove', resize, false); - window.addEventListener('mouseup', stopResize, false); - body.classList.add('sidebar-resizing'); - } - function resize(e) { - var pos = (e.clientX - sidebar.offsetLeft); - if (pos < 20) { - hideSidebar(); + break; + case "ArrowLeft": + e.preventDefault(); + if (html.dir == "rtl") { + next(); } else { - if (body.classList.contains("sidebar-hidden")) { - showSidebar(); - } - pos = Math.min(pos, window.innerWidth - 100); - document.documentElement.style.setProperty('--sidebar-width', pos + 'px'); + prev(); } + break; } - //on mouseup remove windows functions mousemove & mouseup - function stopResize(e) { - body.classList.remove('sidebar-resizing'); - window.removeEventListener('mousemove', resize, false); - window.removeEventListener('mouseup', stopResize, false); - } - - document.addEventListener('touchstart', function (e) { - firstContact = { - x: e.touches[0].clientX, - time: Date.now() - }; - }, { passive: true }); - - document.addEventListener('touchmove', function (e) { - if (!firstContact) - return; - - var curX = e.touches[0].clientX; - var xDiff = curX - firstContact.x, - tDiff = Date.now() - firstContact.time; - - if (tDiff < 250 && Math.abs(xDiff) >= 150) { - if (xDiff >= 0 && firstContact.x < Math.min(document.body.clientWidth * 0.25, 300)) - showSidebar(); - else if (xDiff < 0 && curX < 300) - hideSidebar(); - - firstContact = null; - } - }, { passive: true }); -})(); - -(function chapterNavigation() { - document.addEventListener('keydown', function (e) { - if (e.altKey || e.ctrlKey || e.metaKey || e.shiftKey) { return; } - if (window.search && window.search.hasFocus()) { return; } - var html = document.querySelector('html'); - - function next() { - var nextButton = document.querySelector('.nav-chapters.next'); - if (nextButton) { - window.location.href = nextButton.href; - } - } - function prev() { - var previousButton = document.querySelector('.nav-chapters.previous'); - if (previousButton) { - window.location.href = previousButton.href; - } - } - switch (e.key) { - case 'ArrowRight': - e.preventDefault(); - if (html.dir == 'rtl') { - prev(); - } else { - next(); - } - break; - case 'ArrowLeft': - e.preventDefault(); - if (html.dir == 'rtl') { - next(); - } else { - prev(); - } - break; - } - }); + }); })(); (function clipboard() { - var clipButtons = document.querySelectorAll('.clip-button'); - - function hideTooltip(elem) { - elem.firstChild.innerText = ""; - elem.className = 'fa fa-copy clip-button'; - } - - function showTooltip(elem, msg) { - elem.firstChild.innerText = msg; - elem.className = 'fa fa-copy tooltipped'; - } - - var clipboardSnippets = new ClipboardJS('.clip-button', { - text: function (trigger) { - hideTooltip(trigger); - let playground = trigger.closest("pre"); - return playground_text(playground, false); - } - }); - - Array.from(clipButtons).forEach(function (clipButton) { - clipButton.addEventListener('mouseout', function (e) { - hideTooltip(e.currentTarget); - }); + var clipButtons = document.querySelectorAll(".clip-button"); + + function hideTooltip(elem) { + elem.firstChild.innerText = ""; + elem.className = "fa fa-copy clip-button"; + } + + function showTooltip(elem, msg) { + elem.firstChild.innerText = msg; + elem.className = "fa fa-copy tooltipped"; + } + + var clipboardSnippets = new ClipboardJS(".clip-button", { + text: function (trigger) { + hideTooltip(trigger); + let playground = trigger.closest("pre"); + return playground_text(playground, false); + }, + }); + + Array.from(clipButtons).forEach(function (clipButton) { + clipButton.addEventListener("mouseout", function (e) { + hideTooltip(e.currentTarget); }); + }); - clipboardSnippets.on('success', function (e) { - e.clearSelection(); - showTooltip(e.trigger, "Copied!"); - }); + clipboardSnippets.on("success", function (e) { + e.clearSelection(); + showTooltip(e.trigger, "Copied!"); + }); - clipboardSnippets.on('error', function (e) { - showTooltip(e.trigger, "Clipboard error!"); - }); + clipboardSnippets.on("error", function (e) { + showTooltip(e.trigger, "Clipboard error!"); + }); })(); -(function scrollToTop () { - var menuTitle = document.querySelector('.menu-title'); +(function scrollToTop() { + var menuTitle = document.querySelector(".menu-title"); - menuTitle.addEventListener('click', function () { - document.scrollingElement.scrollTo({ top: 0, behavior: 'smooth' }); - }); + menuTitle.addEventListener("click", function () { + document.scrollingElement.scrollTo({ top: 0, behavior: "smooth" }); + }); })(); (function controllMenu() { - var menu = document.getElementById('menu-bar'); - - (function controllPosition() { - var scrollTop = document.scrollingElement.scrollTop; - var prevScrollTop = scrollTop; - var minMenuY = -menu.clientHeight - 50; - // When the script loads, the page can be at any scroll (e.g. if you reforesh it). - menu.style.top = scrollTop + 'px'; - // Same as parseInt(menu.style.top.slice(0, -2), but faster - var topCache = menu.style.top.slice(0, -2); - menu.classList.remove('sticky'); - var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster - document.addEventListener('scroll', function () { - scrollTop = Math.max(document.scrollingElement.scrollTop, 0); - // `null` means that it doesn't need to be updated - var nextSticky = null; - var nextTop = null; - var scrollDown = scrollTop > prevScrollTop; - var menuPosAbsoluteY = topCache - scrollTop; - if (scrollDown) { - nextSticky = false; - if (menuPosAbsoluteY > 0) { - nextTop = prevScrollTop; - } - } else { - if (menuPosAbsoluteY > 0) { - nextSticky = true; - } else if (menuPosAbsoluteY < minMenuY) { - nextTop = prevScrollTop + minMenuY; - } - } - if (nextSticky === true && stickyCache === false) { - menu.classList.add('sticky'); - stickyCache = true; - } else if (nextSticky === false && stickyCache === true) { - menu.classList.remove('sticky'); - stickyCache = false; - } - if (nextTop !== null) { - menu.style.top = nextTop + 'px'; - topCache = nextTop; - } - prevScrollTop = scrollTop; - }, { passive: true }); - })(); - (function controllBorder() { - function updateBorder() { - if (menu.offsetTop === 0) { - menu.classList.remove('bordered'); - } else { - menu.classList.add('bordered'); - } + var menu = document.getElementById("menu-bar"); + + (function controllPosition() { + var scrollTop = document.scrollingElement.scrollTop; + var prevScrollTop = scrollTop; + var minMenuY = -menu.clientHeight - 50; + // When the script loads, the page can be at any scroll (e.g. if you reforesh it). + menu.style.top = scrollTop + "px"; + // Same as parseInt(menu.style.top.slice(0, -2), but faster + var topCache = menu.style.top.slice(0, -2); + menu.classList.remove("sticky"); + var stickyCache = false; // Same as menu.classList.contains('sticky'), but faster + document.addEventListener( + "scroll", + function () { + scrollTop = Math.max(document.scrollingElement.scrollTop, 0); + // `null` means that it doesn't need to be updated + var nextSticky = null; + var nextTop = null; + var scrollDown = scrollTop > prevScrollTop; + var menuPosAbsoluteY = topCache - scrollTop; + if (scrollDown) { + nextSticky = false; + if (menuPosAbsoluteY > 0) { + nextTop = prevScrollTop; + } + } else { + if (menuPosAbsoluteY > 0) { + nextSticky = true; + } else if (menuPosAbsoluteY < minMenuY) { + nextTop = prevScrollTop + minMenuY; + } + } + if (nextSticky === true && stickyCache === false) { + menu.classList.add("sticky"); + stickyCache = true; + } else if (nextSticky === false && stickyCache === true) { + menu.classList.remove("sticky"); + stickyCache = false; + } + if (nextTop !== null) { + menu.style.top = nextTop + "px"; + topCache = nextTop; } - updateBorder(); - document.addEventListener('scroll', updateBorder, { passive: true }); - })(); + prevScrollTop = scrollTop; + }, + { passive: true } + ); + })(); + (function controllBorder() { + function updateBorder() { + if (menu.offsetTop === 0) { + menu.classList.remove("bordered"); + } else { + menu.classList.add("bordered"); + } + } + updateBorder(); + document.addEventListener("scroll", updateBorder, { passive: true }); + })(); })(); diff --git a/theme/css/variables.css b/theme/css/variables.css index 21bf8e55..70698792 100644 --- a/theme/css/variables.css +++ b/theme/css/variables.css @@ -212,6 +212,46 @@ --search-mark-bg: #e69f67; } +.cairo { + --bg: hsl(60, 9%, 87%); + --fg: #262625; + + --sidebar-bg: #3b2e2a; + --sidebar-fg: #c8c9db; + --sidebar-non-existant: #505254; + --sidebar-active: #e69f67; + --sidebar-spacer: #45373a; + + --scrollbar: var(--sidebar-fg); + + --icons: #737480; + --icons-hover: #262625; + + --links: #2b79a2; + + --inline-code-color: #6e6b5e; + + --theme-popup-bg: #e1e1db; + --theme-popup-border: #b38f6b; + --theme-hover: #99908a; + + --quote-bg: hsl(60, 5%, 75%); + --quote-border: hsl(60, 5%, 70%); + + --table-border-color: hsl(60, 9%, 82%); + --table-header-bg: #b3a497; + --table-alternate-bg: hsl(60, 9%, 84%); + + --searchbar-border-color: #aaa; + --searchbar-bg: #fafafa; + --searchbar-fg: #000; + --searchbar-shadow-color: #aaa; + --searchresults-header-fg: #666; + --searchresults-border-color: #888; + --searchresults-li-bg: #dec2a2; + --search-mark-bg: #e69f67; +} + @media (prefers-color-scheme: dark) { .light.no-js { --bg: hsl(200, 7%, 8%); diff --git a/theme/highlight.js b/theme/highlight.js index 180385b7..7e4475be 100644 --- a/theme/highlight.js +++ b/theme/highlight.js @@ -3,4 +3,2008 @@ License: BSD-3-Clause Copyright (c) 2006-2020, Ivan Sagalaev */ -var hljs=function(){"use strict";function e(n){Object.freeze(n);var t="function"==typeof n;return Object.getOwnPropertyNames(n).forEach((function(r){!Object.hasOwnProperty.call(n,r)||null===n[r]||"object"!=typeof n[r]&&"function"!=typeof n[r]||t&&("caller"===r||"callee"===r||"arguments"===r)||Object.isFrozen(n[r])||e(n[r])})),n}class n{constructor(e){void 0===e.data&&(e.data={}),this.data=e.data}ignoreMatch(){this.ignore=!0}}function t(e){return e.replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'")}function r(e,...n){var t={};for(const n in e)t[n]=e[n];return n.forEach((function(e){for(const n in e)t[n]=e[n]})),t}function a(e){return e.nodeName.toLowerCase()}var i=Object.freeze({__proto__:null,escapeHTML:t,inherit:r,nodeStream:function(e){var n=[];return function e(t,r){for(var i=t.firstChild;i;i=i.nextSibling)3===i.nodeType?r+=i.nodeValue.length:1===i.nodeType&&(n.push({event:"start",offset:r,node:i}),r=e(i,r),a(i).match(/br|hr|img|input/)||n.push({event:"stop",offset:r,node:i}));return r}(e,0),n},mergeStreams:function(e,n,r){var i=0,s="",o=[];function l(){return e.length&&n.length?e[0].offset!==n[0].offset?e[0].offset"}function u(e){s+=""}function d(e){("start"===e.event?c:u)(e.node)}for(;e.length||n.length;){var g=l();if(s+=t(r.substring(i,g[0].offset)),i=g[0].offset,g===e){o.reverse().forEach(u);do{d(g.splice(0,1)[0]),g=l()}while(g===e&&g.length&&g[0].offset===i);o.reverse().forEach(c)}else"start"===g[0].event?o.push(g[0].node):o.pop(),d(g.splice(0,1)[0])}return s+t(r.substr(i))}});const s="",o=e=>!!e.kind;class l{constructor(e,n){this.buffer="",this.classPrefix=n.classPrefix,e.walk(this)}addText(e){this.buffer+=t(e)}openNode(e){if(!o(e))return;let n=e.kind;e.sublanguage||(n=`${this.classPrefix}${n}`),this.span(n)}closeNode(e){o(e)&&(this.buffer+=s)}value(){return this.buffer}span(e){this.buffer+=``}}class c{constructor(){this.rootNode={children:[]},this.stack=[this.rootNode]}get top(){return this.stack[this.stack.length-1]}get root(){return this.rootNode}add(e){this.top.children.push(e)}openNode(e){const n={kind:e,children:[]};this.add(n),this.stack.push(n)}closeNode(){if(this.stack.length>1)return this.stack.pop()}closeAllNodes(){for(;this.closeNode(););}toJSON(){return JSON.stringify(this.rootNode,null,4)}walk(e){return this.constructor._walk(e,this.rootNode)}static _walk(e,n){return"string"==typeof n?e.addText(n):n.children&&(e.openNode(n),n.children.forEach(n=>this._walk(e,n)),e.closeNode(n)),e}static _collapse(e){"string"!=typeof e&&e.children&&(e.children.every(e=>"string"==typeof e)?e.children=[e.children.join("")]:e.children.forEach(e=>{c._collapse(e)}))}}class u extends c{constructor(e){super(),this.options=e}addKeyword(e,n){""!==e&&(this.openNode(n),this.addText(e),this.closeNode())}addText(e){""!==e&&this.add(e)}addSublanguage(e,n){const t=e.root;t.kind=n,t.sublanguage=!0,this.add(t)}toHTML(){return new l(this,this.options).value()}finalize(){return!0}}function d(e){return e?"string"==typeof e?e:e.source:null}const g="(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)",h={begin:"\\\\[\\s\\S]",relevance:0},f={className:"string",begin:"'",end:"'",illegal:"\\n",contains:[h]},p={className:"string",begin:'"',end:'"',illegal:"\\n",contains:[h]},b={begin:/\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/},m=function(e,n,t={}){var a=r({className:"comment",begin:e,end:n,contains:[]},t);return a.contains.push(b),a.contains.push({className:"doctag",begin:"(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):",relevance:0}),a},v=m("//","$"),x=m("/\\*","\\*/"),E=m("#","$");var _=Object.freeze({__proto__:null,IDENT_RE:"[a-zA-Z]\\w*",UNDERSCORE_IDENT_RE:"[a-zA-Z_]\\w*",NUMBER_RE:"\\b\\d+(\\.\\d+)?",C_NUMBER_RE:g,BINARY_NUMBER_RE:"\\b(0b[01]+)",RE_STARTERS_RE:"!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~",SHEBANG:(e={})=>{const n=/^#![ ]*\//;return e.binary&&(e.begin=function(...e){return e.map(e=>d(e)).join("")}(n,/.*\b/,e.binary,/\b.*/)),r({className:"meta",begin:n,end:/$/,relevance:0,"on:begin":(e,n)=>{0!==e.index&&n.ignoreMatch()}},e)},BACKSLASH_ESCAPE:h,APOS_STRING_MODE:f,QUOTE_STRING_MODE:p,PHRASAL_WORDS_MODE:b,COMMENT:m,C_LINE_COMMENT_MODE:v,C_BLOCK_COMMENT_MODE:x,HASH_COMMENT_MODE:E,NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?",relevance:0},C_NUMBER_MODE:{className:"number",begin:g,relevance:0},BINARY_NUMBER_MODE:{className:"number",begin:"\\b(0b[01]+)",relevance:0},CSS_NUMBER_MODE:{className:"number",begin:"\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?",relevance:0},REGEXP_MODE:{begin:/(?=\/[^/\n]*\/)/,contains:[{className:"regexp",begin:/\//,end:/\/[gimuy]*/,illegal:/\n/,contains:[h,{begin:/\[/,end:/\]/,relevance:0,contains:[h]}]}]},TITLE_MODE:{className:"title",begin:"[a-zA-Z]\\w*",relevance:0},UNDERSCORE_TITLE_MODE:{className:"title",begin:"[a-zA-Z_]\\w*",relevance:0},METHOD_GUARD:{begin:"\\.\\s*[a-zA-Z_]\\w*",relevance:0},END_SAME_AS_BEGIN:function(e){return Object.assign(e,{"on:begin":(e,n)=>{n.data._beginMatch=e[1]},"on:end":(e,n)=>{n.data._beginMatch!==e[1]&&n.ignoreMatch()}})}}),N="of and for in not or if then".split(" ");function w(e,n){return n?+n:function(e){return N.includes(e.toLowerCase())}(e)?0:1}const R=t,y=r,{nodeStream:k,mergeStreams:O}=i,M=Symbol("nomatch");return function(t){var a=[],i={},s={},o=[],l=!0,c=/(^(<[^>]+>|\t|)+|\n)/gm,g="Could not find the language '{}', did you forget to load/include a language module?";const h={disableAutodetect:!0,name:"Plain text",contains:[]};var f={noHighlightRe:/^(no-?highlight)$/i,languageDetectRe:/\blang(?:uage)?-([\w-]+)\b/i,classPrefix:"hljs-",tabReplace:null,useBR:!1,languages:null,__emitter:u};function p(e){return f.noHighlightRe.test(e)}function b(e,n,t,r){var a={code:n,language:e};S("before:highlight",a);var i=a.result?a.result:m(a.language,a.code,t,r);return i.code=a.code,S("after:highlight",i),i}function m(e,t,a,s){var o=t;function c(e,n){var t=E.case_insensitive?n[0].toLowerCase():n[0];return Object.prototype.hasOwnProperty.call(e.keywords,t)&&e.keywords[t]}function u(){null!=y.subLanguage?function(){if(""!==A){var e=null;if("string"==typeof y.subLanguage){if(!i[y.subLanguage])return void O.addText(A);e=m(y.subLanguage,A,!0,k[y.subLanguage]),k[y.subLanguage]=e.top}else e=v(A,y.subLanguage.length?y.subLanguage:null);y.relevance>0&&(I+=e.relevance),O.addSublanguage(e.emitter,e.language)}}():function(){if(!y.keywords)return void O.addText(A);let e=0;y.keywordPatternRe.lastIndex=0;let n=y.keywordPatternRe.exec(A),t="";for(;n;){t+=A.substring(e,n.index);const r=c(y,n);if(r){const[e,a]=r;O.addText(t),t="",I+=a,O.addKeyword(n[0],e)}else t+=n[0];e=y.keywordPatternRe.lastIndex,n=y.keywordPatternRe.exec(A)}t+=A.substr(e),O.addText(t)}(),A=""}function h(e){return e.className&&O.openNode(e.className),y=Object.create(e,{parent:{value:y}})}function p(e){return 0===y.matcher.regexIndex?(A+=e[0],1):(L=!0,0)}var b={};function x(t,r){var i=r&&r[0];if(A+=t,null==i)return u(),0;if("begin"===b.type&&"end"===r.type&&b.index===r.index&&""===i){if(A+=o.slice(r.index,r.index+1),!l){const n=Error("0 width match regex");throw n.languageName=e,n.badRule=b.rule,n}return 1}if(b=r,"begin"===r.type)return function(e){var t=e[0],r=e.rule;const a=new n(r),i=[r.__beforeBegin,r["on:begin"]];for(const n of i)if(n&&(n(e,a),a.ignore))return p(t);return r&&r.endSameAsBegin&&(r.endRe=RegExp(t.replace(/[-/\\^$*+?.()|[\]{}]/g,"\\$&"),"m")),r.skip?A+=t:(r.excludeBegin&&(A+=t),u(),r.returnBegin||r.excludeBegin||(A=t)),h(r),r.returnBegin?0:t.length}(r);if("illegal"===r.type&&!a){const e=Error('Illegal lexeme "'+i+'" for mode "'+(y.className||"")+'"');throw e.mode=y,e}if("end"===r.type){var s=function(e){var t=e[0],r=o.substr(e.index),a=function e(t,r,a){let i=function(e,n){var t=e&&e.exec(n);return t&&0===t.index}(t.endRe,a);if(i){if(t["on:end"]){const e=new n(t);t["on:end"](r,e),e.ignore&&(i=!1)}if(i){for(;t.endsParent&&t.parent;)t=t.parent;return t}}if(t.endsWithParent)return e(t.parent,r,a)}(y,e,r);if(!a)return M;var i=y;i.skip?A+=t:(i.returnEnd||i.excludeEnd||(A+=t),u(),i.excludeEnd&&(A=t));do{y.className&&O.closeNode(),y.skip||y.subLanguage||(I+=y.relevance),y=y.parent}while(y!==a.parent);return a.starts&&(a.endSameAsBegin&&(a.starts.endRe=a.endRe),h(a.starts)),i.returnEnd?0:t.length}(r);if(s!==M)return s}if("illegal"===r.type&&""===i)return 1;if(B>1e5&&B>3*r.index)throw Error("potential infinite loop, way more iterations than matches");return A+=i,i.length}var E=T(e);if(!E)throw console.error(g.replace("{}",e)),Error('Unknown language: "'+e+'"');var _=function(e){function n(n,t){return RegExp(d(n),"m"+(e.case_insensitive?"i":"")+(t?"g":""))}class t{constructor(){this.matchIndexes={},this.regexes=[],this.matchAt=1,this.position=0}addRule(e,n){n.position=this.position++,this.matchIndexes[this.matchAt]=n,this.regexes.push([n,e]),this.matchAt+=function(e){return RegExp(e.toString()+"|").exec("").length-1}(e)+1}compile(){0===this.regexes.length&&(this.exec=()=>null);const e=this.regexes.map(e=>e[1]);this.matcherRe=n(function(e,n="|"){for(var t=/\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./,r=0,a="",i=0;i0&&(a+=n),a+="(";o.length>0;){var l=t.exec(o);if(null==l){a+=o;break}a+=o.substring(0,l.index),o=o.substring(l.index+l[0].length),"\\"===l[0][0]&&l[1]?a+="\\"+(+l[1]+s):(a+=l[0],"("===l[0]&&r++)}a+=")"}return a}(e),!0),this.lastIndex=0}exec(e){this.matcherRe.lastIndex=this.lastIndex;const n=this.matcherRe.exec(e);if(!n)return null;const t=n.findIndex((e,n)=>n>0&&void 0!==e),r=this.matchIndexes[t];return n.splice(0,t),Object.assign(n,r)}}class a{constructor(){this.rules=[],this.multiRegexes=[],this.count=0,this.lastIndex=0,this.regexIndex=0}getMatcher(e){if(this.multiRegexes[e])return this.multiRegexes[e];const n=new t;return this.rules.slice(e).forEach(([e,t])=>n.addRule(e,t)),n.compile(),this.multiRegexes[e]=n,n}considerAll(){this.regexIndex=0}addRule(e,n){this.rules.push([e,n]),"begin"===n.type&&this.count++}exec(e){const n=this.getMatcher(this.regexIndex);n.lastIndex=this.lastIndex;const t=n.exec(e);return t&&(this.regexIndex+=t.position+1,this.regexIndex===this.count&&(this.regexIndex=0)),t}}function i(e,n){const t=e.input[e.index-1],r=e.input[e.index+e[0].length];"."!==t&&"."!==r||n.ignoreMatch()}if(e.contains&&e.contains.includes("self"))throw Error("ERR: contains `self` is not supported at the top-level of a language. See documentation.");return function t(s,o){const l=s;if(s.compiled)return l;s.compiled=!0,s.__beforeBegin=null,s.keywords=s.keywords||s.beginKeywords;let c=null;if("object"==typeof s.keywords&&(c=s.keywords.$pattern,delete s.keywords.$pattern),s.keywords&&(s.keywords=function(e,n){var t={};return"string"==typeof e?r("keyword",e):Object.keys(e).forEach((function(n){r(n,e[n])})),t;function r(e,r){n&&(r=r.toLowerCase()),r.split(" ").forEach((function(n){var r=n.split("|");t[r[0]]=[e,w(r[0],r[1])]}))}}(s.keywords,e.case_insensitive)),s.lexemes&&c)throw Error("ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) ");return l.keywordPatternRe=n(s.lexemes||c||/\w+/,!0),o&&(s.beginKeywords&&(s.begin="\\b("+s.beginKeywords.split(" ").join("|")+")(?=\\b|\\s)",s.__beforeBegin=i),s.begin||(s.begin=/\B|\b/),l.beginRe=n(s.begin),s.endSameAsBegin&&(s.end=s.begin),s.end||s.endsWithParent||(s.end=/\B|\b/),s.end&&(l.endRe=n(s.end)),l.terminator_end=d(s.end)||"",s.endsWithParent&&o.terminator_end&&(l.terminator_end+=(s.end?"|":"")+o.terminator_end)),s.illegal&&(l.illegalRe=n(s.illegal)),void 0===s.relevance&&(s.relevance=1),s.contains||(s.contains=[]),s.contains=[].concat(...s.contains.map((function(e){return function(e){return e.variants&&!e.cached_variants&&(e.cached_variants=e.variants.map((function(n){return r(e,{variants:null},n)}))),e.cached_variants?e.cached_variants:function e(n){return!!n&&(n.endsWithParent||e(n.starts))}(e)?r(e,{starts:e.starts?r(e.starts):null}):Object.isFrozen(e)?r(e):e}("self"===e?s:e)}))),s.contains.forEach((function(e){t(e,l)})),s.starts&&t(s.starts,o),l.matcher=function(e){const n=new a;return e.contains.forEach(e=>n.addRule(e.begin,{rule:e,type:"begin"})),e.terminator_end&&n.addRule(e.terminator_end,{type:"end"}),e.illegal&&n.addRule(e.illegal,{type:"illegal"}),n}(l),l}(e)}(E),N="",y=s||_,k={},O=new f.__emitter(f);!function(){for(var e=[],n=y;n!==E;n=n.parent)n.className&&e.unshift(n.className);e.forEach(e=>O.openNode(e))}();var A="",I=0,S=0,B=0,L=!1;try{for(y.matcher.considerAll();;){B++,L?L=!1:(y.matcher.lastIndex=S,y.matcher.considerAll());const e=y.matcher.exec(o);if(!e)break;const n=x(o.substring(S,e.index),e);S=e.index+n}return x(o.substr(S)),O.closeAllNodes(),O.finalize(),N=O.toHTML(),{relevance:I,value:N,language:e,illegal:!1,emitter:O,top:y}}catch(n){if(n.message&&n.message.includes("Illegal"))return{illegal:!0,illegalBy:{msg:n.message,context:o.slice(S-100,S+100),mode:n.mode},sofar:N,relevance:0,value:R(o),emitter:O};if(l)return{illegal:!1,relevance:0,value:R(o),emitter:O,language:e,top:y,errorRaised:n};throw n}}function v(e,n){n=n||f.languages||Object.keys(i);var t=function(e){const n={relevance:0,emitter:new f.__emitter(f),value:R(e),illegal:!1,top:h};return n.emitter.addText(e),n}(e),r=t;return n.filter(T).filter(I).forEach((function(n){var a=m(n,e,!1);a.language=n,a.relevance>r.relevance&&(r=a),a.relevance>t.relevance&&(r=t,t=a)})),r.language&&(t.second_best=r),t}function x(e){return f.tabReplace||f.useBR?e.replace(c,e=>"\n"===e?f.useBR?"
":e:f.tabReplace?e.replace(/\t/g,f.tabReplace):e):e}function E(e){let n=null;const t=function(e){var n=e.className+" ";n+=e.parentNode?e.parentNode.className:"";const t=f.languageDetectRe.exec(n);if(t){var r=T(t[1]);return r||(console.warn(g.replace("{}",t[1])),console.warn("Falling back to no-highlight mode for this block.",e)),r?t[1]:"no-highlight"}return n.split(/\s+/).find(e=>p(e)||T(e))}(e);if(p(t))return;S("before:highlightBlock",{block:e,language:t}),f.useBR?(n=document.createElement("div")).innerHTML=e.innerHTML.replace(/\n/g,"").replace(//g,"\n"):n=e;const r=n.textContent,a=t?b(t,r,!0):v(r),i=k(n);if(i.length){const e=document.createElement("div");e.innerHTML=a.value,a.value=O(i,k(e),r)}a.value=x(a.value),S("after:highlightBlock",{block:e,result:a}),e.innerHTML=a.value,e.className=function(e,n,t){var r=n?s[n]:t,a=[e.trim()];return e.match(/\bhljs\b/)||a.push("hljs"),e.includes(r)||a.push(r),a.join(" ").trim()}(e.className,t,a.language),e.result={language:a.language,re:a.relevance,relavance:a.relevance},a.second_best&&(e.second_best={language:a.second_best.language,re:a.second_best.relevance,relavance:a.second_best.relevance})}const N=()=>{if(!N.called){N.called=!0;var e=document.querySelectorAll("pre code");a.forEach.call(e,E)}};function T(e){return e=(e||"").toLowerCase(),i[e]||i[s[e]]}function A(e,{languageName:n}){"string"==typeof e&&(e=[e]),e.forEach(e=>{s[e]=n})}function I(e){var n=T(e);return n&&!n.disableAutodetect}function S(e,n){var t=e;o.forEach((function(e){e[t]&&e[t](n)}))}Object.assign(t,{highlight:b,highlightAuto:v,fixMarkup:x,highlightBlock:E,configure:function(e){f=y(f,e)},initHighlighting:N,initHighlightingOnLoad:function(){window.addEventListener("DOMContentLoaded",N,!1)},registerLanguage:function(e,n){var r=null;try{r=n(t)}catch(n){if(console.error("Language definition for '{}' could not be registered.".replace("{}",e)),!l)throw n;console.error(n),r=h}r.name||(r.name=e),i[e]=r,r.rawDefinition=n.bind(null,t),r.aliases&&A(r.aliases,{languageName:e})},listLanguages:function(){return Object.keys(i)},getLanguage:T,registerAliases:A,requireLanguage:function(e){var n=T(e);if(n)return n;throw Error("The '{}' language is required, but not loaded.".replace("{}",e))},autoDetection:I,inherit:y,addPlugin:function(e){o.push(e)}}),t.debugMode=function(){l=!1},t.safeMode=function(){l=!0},t.versionString="10.1.1";for(const n in _)"object"==typeof _[n]&&e(_[n]);return Object.assign(t,_),t}({})}();"object"==typeof exports&&"undefined"!=typeof module&&(module.exports=hljs);hljs.registerLanguage("php",function(){"use strict";return function(e){var r={begin:"\\$+[a-zA-Z_-ÿ][a-zA-Z0-9_-ÿ]*"},t={className:"meta",variants:[{begin:/<\?php/,relevance:10},{begin:/<\?[=]?/},{begin:/\?>/}]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:'b"',end:'"'},{begin:"b'",end:"'"},e.inherit(e.APOS_STRING_MODE,{illegal:null}),e.inherit(e.QUOTE_STRING_MODE,{illegal:null})]},n={variants:[e.BINARY_NUMBER_MODE,e.C_NUMBER_MODE]},i={keyword:"__CLASS__ __DIR__ __FILE__ __FUNCTION__ __LINE__ __METHOD__ __NAMESPACE__ __TRAIT__ die echo exit include include_once print require require_once array abstract and as binary bool boolean break callable case catch class clone const continue declare default do double else elseif empty enddeclare endfor endforeach endif endswitch endwhile eval extends final finally float for foreach from global goto if implements instanceof insteadof int integer interface isset iterable list new object or private protected public real return string switch throw trait try unset use var void while xor yield",literal:"false null true",built_in:"Error|0 AppendIterator ArgumentCountError ArithmeticError ArrayIterator ArrayObject AssertionError BadFunctionCallException BadMethodCallException CachingIterator CallbackFilterIterator CompileError Countable DirectoryIterator DivisionByZeroError DomainException EmptyIterator ErrorException Exception FilesystemIterator FilterIterator GlobIterator InfiniteIterator InvalidArgumentException IteratorIterator LengthException LimitIterator LogicException MultipleIterator NoRewindIterator OutOfBoundsException OutOfRangeException OuterIterator OverflowException ParentIterator ParseError RangeException RecursiveArrayIterator RecursiveCachingIterator RecursiveCallbackFilterIterator RecursiveDirectoryIterator RecursiveFilterIterator RecursiveIterator RecursiveIteratorIterator RecursiveRegexIterator RecursiveTreeIterator RegexIterator RuntimeException SeekableIterator SplDoublyLinkedList SplFileInfo SplFileObject SplFixedArray SplHeap SplMaxHeap SplMinHeap SplObjectStorage SplObserver SplObserver SplPriorityQueue SplQueue SplStack SplSubject SplSubject SplTempFileObject TypeError UnderflowException UnexpectedValueException ArrayAccess Closure Generator Iterator IteratorAggregate Serializable Throwable Traversable WeakReference Directory __PHP_Incomplete_Class parent php_user_filter self static stdClass"};return{aliases:["php","php3","php4","php5","php6","php7"],case_insensitive:!0,keywords:i,contains:[e.HASH_COMMENT_MODE,e.COMMENT("//","$",{contains:[t]}),e.COMMENT("/\\*","\\*/",{contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.COMMENT("__halt_compiler.+?;",!1,{endsWithParent:!0,keywords:"__halt_compiler"}),{className:"string",begin:/<<<['"]?\w+['"]?$/,end:/^\w+;?$/,contains:[e.BACKSLASH_ESCAPE,{className:"subst",variants:[{begin:/\$\w+/},{begin:/\{\$/,end:/\}/}]}]},t,{className:"keyword",begin:/\$this\b/},r,{begin:/(::|->)+[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*/},{className:"function",beginKeywords:"fn function",end:/[;{]/,excludeEnd:!0,illegal:"[$%\\[]",contains:[e.UNDERSCORE_TITLE_MODE,{className:"params",begin:"\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0,keywords:i,contains:["self",r,e.C_BLOCK_COMMENT_MODE,a,n]}]},{className:"class",beginKeywords:"class interface",end:"{",excludeEnd:!0,illegal:/[:\(\$"]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"namespace",end:";",illegal:/[\.']/,contains:[e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"use",end:";",contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"=>"},a,n]}}}());hljs.registerLanguage("nginx",function(){"use strict";return function(e){var n={className:"variable",variants:[{begin:/\$\d+/},{begin:/\$\{/,end:/}/},{begin:"[\\$\\@]"+e.UNDERSCORE_IDENT_RE}]},a={endsWithParent:!0,keywords:{$pattern:"[a-z/_]+",literal:"on off yes no true false none blocked debug info notice warn error crit select break last permanent redirect kqueue rtsig epoll poll /dev/poll"},relevance:0,illegal:"=>",contains:[e.HASH_COMMENT_MODE,{className:"string",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:/"/,end:/"/},{begin:/'/,end:/'/}]},{begin:"([a-z]+):/",end:"\\s",endsWithParent:!0,excludeEnd:!0,contains:[n]},{className:"regexp",contains:[e.BACKSLASH_ESCAPE,n],variants:[{begin:"\\s\\^",end:"\\s|{|;",returnEnd:!0},{begin:"~\\*?\\s+",end:"\\s|{|;",returnEnd:!0},{begin:"\\*(\\.[a-z\\-]+)+"},{begin:"([a-z\\-]+\\.)+\\*"}]},{className:"number",begin:"\\b\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?\\b"},{className:"number",begin:"\\b\\d+[kKmMgGdshdwy]*\\b",relevance:0},n]};return{name:"Nginx config",aliases:["nginxconf"],contains:[e.HASH_COMMENT_MODE,{begin:e.UNDERSCORE_IDENT_RE+"\\s+{",returnBegin:!0,end:"{",contains:[{className:"section",begin:e.UNDERSCORE_IDENT_RE}],relevance:0},{begin:e.UNDERSCORE_IDENT_RE+"\\s",end:";|{",returnBegin:!0,contains:[{className:"attribute",begin:e.UNDERSCORE_IDENT_RE,starts:a}],relevance:0}],illegal:"[^\\s\\}]"}}}());hljs.registerLanguage("csharp",function(){"use strict";return function(e){var n={keyword:"abstract as base bool break byte case catch char checked const continue decimal default delegate do double enum event explicit extern finally fixed float for foreach goto if implicit in int interface internal is lock long object operator out override params private protected public readonly ref sbyte sealed short sizeof stackalloc static string struct switch this try typeof uint ulong unchecked unsafe ushort using virtual void volatile while add alias ascending async await by descending dynamic equals from get global group into join let nameof on orderby partial remove select set value var when where yield",literal:"null false true"},i=e.inherit(e.TITLE_MODE,{begin:"[a-zA-Z](\\.?\\w)*"}),a={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"string",begin:'@"',end:'"',contains:[{begin:'""'}]},t=e.inherit(s,{illegal:/\n/}),l={className:"subst",begin:"{",end:"}",keywords:n},r=e.inherit(l,{illegal:/\n/}),c={className:"string",begin:/\$"/,end:'"',illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},e.BACKSLASH_ESCAPE,r]},o={className:"string",begin:/\$@"/,end:'"',contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},l]},g=e.inherit(o,{illegal:/\n/,contains:[{begin:"{{"},{begin:"}}"},{begin:'""'},r]});l.contains=[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.C_BLOCK_COMMENT_MODE],r.contains=[g,c,t,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,a,e.inherit(e.C_BLOCK_COMMENT_MODE,{illegal:/\n/})];var d={variants:[o,c,s,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},E={begin:"<",end:">",contains:[{beginKeywords:"in out"},i]},_=e.IDENT_RE+"(<"+e.IDENT_RE+"(\\s*,\\s*"+e.IDENT_RE+")*>)?(\\[\\])?",b={begin:"@"+e.IDENT_RE,relevance:0};return{name:"C#",aliases:["cs","c#"],keywords:n,illegal:/::/,contains:[e.COMMENT("///","$",{returnBegin:!0,contains:[{className:"doctag",variants:[{begin:"///",relevance:0},{begin:"\x3c!--|--\x3e"},{begin:""}]}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"meta",begin:"#",end:"$",keywords:{"meta-keyword":"if else elif endif define undef warning error line region endregion pragma checksum"}},d,a,{beginKeywords:"class interface",end:/[{;=]/,illegal:/[^\s:,]/,contains:[{beginKeywords:"where class"},i,E,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{beginKeywords:"namespace",end:/[{;=]/,illegal:/[^\s:]/,contains:[i,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"meta",begin:"^\\s*\\[",excludeBegin:!0,end:"\\]",excludeEnd:!0,contains:[{className:"meta-string",begin:/"/,end:/"/}]},{beginKeywords:"new return throw await else",relevance:0},{className:"function",begin:"("+_+"\\s+)+"+e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,end:/\s*[{;=]/,excludeEnd:!0,keywords:n,contains:[{begin:e.IDENT_RE+"\\s*(\\<.+\\>)?\\s*\\(",returnBegin:!0,contains:[e.TITLE_MODE,E],relevance:0},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:n,relevance:0,contains:[d,a,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},b]}}}());hljs.registerLanguage("perl",function(){"use strict";return function(e){var n={$pattern:/[\w.]+/,keyword:"getpwent getservent quotemeta msgrcv scalar kill dbmclose undef lc ma syswrite tr send umask sysopen shmwrite vec qx utime local oct semctl localtime readpipe do return format read sprintf dbmopen pop getpgrp not getpwnam rewinddir qq fileno qw endprotoent wait sethostent bless s|0 opendir continue each sleep endgrent shutdown dump chomp connect getsockname die socketpair close flock exists index shmget sub for endpwent redo lstat msgctl setpgrp abs exit select print ref gethostbyaddr unshift fcntl syscall goto getnetbyaddr join gmtime symlink semget splice x|0 getpeername recv log setsockopt cos last reverse gethostbyname getgrnam study formline endhostent times chop length gethostent getnetent pack getprotoent getservbyname rand mkdir pos chmod y|0 substr endnetent printf next open msgsnd readdir use unlink getsockopt getpriority rindex wantarray hex system getservbyport endservent int chr untie rmdir prototype tell listen fork shmread ucfirst setprotoent else sysseek link getgrgid shmctl waitpid unpack getnetbyname reset chdir grep split require caller lcfirst until warn while values shift telldir getpwuid my getprotobynumber delete and sort uc defined srand accept package seekdir getprotobyname semop our rename seek if q|0 chroot sysread setpwent no crypt getc chown sqrt write setnetent setpriority foreach tie sin msgget map stat getlogin unless elsif truncate exec keys glob tied closedir ioctl socket readlink eval xor readline binmode setservent eof ord bind alarm pipe atan2 getgrent exp time push setgrent gt lt or ne m|0 break given say state when"},t={className:"subst",begin:"[$@]\\{",end:"\\}",keywords:n},s={begin:"->{",end:"}"},r={variants:[{begin:/\$\d/},{begin:/[\$%@](\^\w\b|#\w+(::\w+)*|{\w+}|\w+(::\w*)*)/},{begin:/[\$%@][^\s\w{]/,relevance:0}]},i=[e.BACKSLASH_ESCAPE,t,r],a=[r,e.HASH_COMMENT_MODE,e.COMMENT("^\\=\\w","\\=cut",{endsWithParent:!0}),s,{className:"string",contains:i,variants:[{begin:"q[qwxr]?\\s*\\(",end:"\\)",relevance:5},{begin:"q[qwxr]?\\s*\\[",end:"\\]",relevance:5},{begin:"q[qwxr]?\\s*\\{",end:"\\}",relevance:5},{begin:"q[qwxr]?\\s*\\|",end:"\\|",relevance:5},{begin:"q[qwxr]?\\s*\\<",end:"\\>",relevance:5},{begin:"qw\\s+q",end:"q",relevance:5},{begin:"'",end:"'",contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"'},{begin:"`",end:"`",contains:[e.BACKSLASH_ESCAPE]},{begin:"{\\w+}",contains:[],relevance:0},{begin:"-?\\w+\\s*\\=\\>",contains:[],relevance:0}]},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\/\\/|"+e.RE_STARTERS_RE+"|\\b(split|return|print|reverse|grep)\\b)\\s*",keywords:"split return print reverse grep",relevance:0,contains:[e.HASH_COMMENT_MODE,{className:"regexp",begin:"(s|tr|y)/(\\\\.|[^/])*/(\\\\.|[^/])*/[a-z]*",relevance:10},{className:"regexp",begin:"(m|qr)?/",end:"/[a-z]*",contains:[e.BACKSLASH_ESCAPE],relevance:0}]},{className:"function",beginKeywords:"sub",end:"(\\s*\\(.*?\\))?[;{]",excludeEnd:!0,relevance:5,contains:[e.TITLE_MODE]},{begin:"-\\w\\b",relevance:0},{begin:"^__DATA__$",end:"^__END__$",subLanguage:"mojolicious",contains:[{begin:"^@@.*",end:"$",className:"comment"}]}];return t.contains=a,s.contains=a,{name:"Perl",aliases:["pl","pm"],keywords:n,contains:a}}}());hljs.registerLanguage("swift",function(){"use strict";return function(e){var i={keyword:"#available #colorLiteral #column #else #elseif #endif #file #fileLiteral #function #if #imageLiteral #line #selector #sourceLocation _ __COLUMN__ __FILE__ __FUNCTION__ __LINE__ Any as as! as? associatedtype associativity break case catch class continue convenience default defer deinit didSet do dynamic dynamicType else enum extension fallthrough false fileprivate final for func get guard if import in indirect infix init inout internal is lazy left let mutating nil none nonmutating open operator optional override postfix precedence prefix private protocol Protocol public repeat required rethrows return right self Self set static struct subscript super switch throw throws true try try! try? Type typealias unowned var weak where while willSet",literal:"true false nil",built_in:"abs advance alignof alignofValue anyGenerator assert assertionFailure bridgeFromObjectiveC bridgeFromObjectiveCUnconditional bridgeToObjectiveC bridgeToObjectiveCUnconditional c compactMap contains count countElements countLeadingZeros debugPrint debugPrintln distance dropFirst dropLast dump encodeBitsAsWords enumerate equal fatalError filter find getBridgedObjectiveCType getVaList indices insertionSort isBridgedToObjectiveC isBridgedVerbatimToObjectiveC isUniquelyReferenced isUniquelyReferencedNonObjC join lazy lexicographicalCompare map max maxElement min minElement numericCast overlaps partition posix precondition preconditionFailure print println quickSort readLine reduce reflect reinterpretCast reverse roundUpToAlignment sizeof sizeofValue sort split startsWith stride strideof strideofValue swap toString transcode underestimateCount unsafeAddressOf unsafeBitCast unsafeDowncast unsafeUnwrap unsafeReflect withExtendedLifetime withObjectAtPlusZero withUnsafePointer withUnsafePointerToObject withUnsafeMutablePointer withUnsafeMutablePointers withUnsafePointer withUnsafePointers withVaList zip"},n=e.COMMENT("/\\*","\\*/",{contains:["self"]}),t={className:"subst",begin:/\\\(/,end:"\\)",keywords:i,contains:[]},a={className:"string",contains:[e.BACKSLASH_ESCAPE,t],variants:[{begin:/"""/,end:/"""/},{begin:/"/,end:/"/}]},r={className:"number",begin:"\\b([\\d_]+(\\.[\\deE_]+)?|0x[a-fA-F0-9_]+(\\.[a-fA-F0-9p_]+)?|0b[01_]+|0o[0-7_]+)\\b",relevance:0};return t.contains=[r],{name:"Swift",keywords:i,contains:[a,e.C_LINE_COMMENT_MODE,n,{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*[!?]"},{className:"type",begin:"\\b[A-Z][\\wÀ-ʸ']*",relevance:0},r,{className:"function",beginKeywords:"func",end:"{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][0-9A-Za-z$_]*/}),{begin://},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:i,contains:["self",r,a,e.C_BLOCK_COMMENT_MODE,{begin:":"}],illegal:/["']/}],illegal:/\[|%/},{className:"class",beginKeywords:"struct protocol class extension enum",keywords:i,end:"\\{",excludeEnd:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/[A-Za-z$_][\u00C0-\u02B80-9A-Za-z$_]*/})]},{className:"meta",begin:"(@discardableResult|@warn_unused_result|@exported|@lazy|@noescape|@NSCopying|@NSManaged|@objc|@objcMembers|@convention|@required|@noreturn|@IBAction|@IBDesignable|@IBInspectable|@IBOutlet|@infix|@prefix|@postfix|@autoclosure|@testable|@available|@nonobjc|@NSApplicationMain|@UIApplicationMain|@dynamicMemberLookup|@propertyWrapper)\\b"},{beginKeywords:"import",end:/$/,contains:[e.C_LINE_COMMENT_MODE,n]}]}}}());hljs.registerLanguage("makefile",function(){"use strict";return function(e){var i={className:"variable",variants:[{begin:"\\$\\("+e.UNDERSCORE_IDENT_RE+"\\)",contains:[e.BACKSLASH_ESCAPE]},{begin:/\$[@%`]+/}]}]}]};return{name:"HTML, XML",aliases:["html","xhtml","rss","atom","xjb","xsd","xsl","plist","wsf","svg"],case_insensitive:!0,contains:[{className:"meta",begin:"",relevance:10,contains:[a,i,t,s,{begin:"\\[",end:"\\]",contains:[{className:"meta",begin:"",contains:[a,s,i,t]}]}]},e.COMMENT("\x3c!--","--\x3e",{relevance:10}),{begin:"<\\!\\[CDATA\\[",end:"\\]\\]>",relevance:10},n,{className:"meta",begin:/<\?xml/,end:/\?>/,relevance:10},{className:"tag",begin:")",end:">",keywords:{name:"style"},contains:[c],starts:{end:"",returnEnd:!0,subLanguage:["css","xml"]}},{className:"tag",begin:")",end:">",keywords:{name:"script"},contains:[c],starts:{end:"<\/script>",returnEnd:!0,subLanguage:["javascript","handlebars","xml"]}},{className:"tag",begin:"",contains:[{className:"name",begin:/[^\/><\s]+/,relevance:0},c]}]}}}());hljs.registerLanguage("bash",function(){"use strict";return function(e){const s={};Object.assign(s,{className:"variable",variants:[{begin:/\$[\w\d#@][\w\d_]*/},{begin:/\$\{/,end:/\}/,contains:[{begin:/:-/,contains:[s]}]}]});const t={className:"subst",begin:/\$\(/,end:/\)/,contains:[e.BACKSLASH_ESCAPE]},n={className:"string",begin:/"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,t]};t.contains.push(n);const a={begin:/\$\(\(/,end:/\)\)/,contains:[{begin:/\d+#[0-9a-f]+/,className:"number"},e.NUMBER_MODE,s]},i=e.SHEBANG({binary:"(fish|bash|zsh|sh|csh|ksh|tcsh|dash|scsh)",relevance:10}),c={className:"function",begin:/\w[\w\d_]*\s*\(\s*\)\s*\{/,returnBegin:!0,contains:[e.inherit(e.TITLE_MODE,{begin:/\w[\w\d_]*/})],relevance:0};return{name:"Bash",aliases:["sh","zsh"],keywords:{$pattern:/\b-?[a-z\._]+\b/,keyword:"if then else elif fi for while in do done case esac function",literal:"true false",built_in:"break cd continue eval exec exit export getopts hash pwd readonly return shift test times trap umask unset alias bind builtin caller command declare echo enable help let local logout mapfile printf read readarray source type typeset ulimit unalias set shopt autoload bg bindkey bye cap chdir clone comparguments compcall compctl compdescribe compfiles compgroups compquote comptags comptry compvalues dirs disable disown echotc echoti emulate fc fg float functions getcap getln history integer jobs kill limit log noglob popd print pushd pushln rehash sched setcap setopt stat suspend ttyctl unfunction unhash unlimit unsetopt vared wait whence where which zcompile zformat zftp zle zmodload zparseopts zprof zpty zregexparse zsocket zstyle ztcp",_:"-ne -eq -lt -gt -f -d -e -s -l -a"},contains:[i,e.SHEBANG(),c,a,e.HASH_COMMENT_MODE,n,{className:"",begin:/\\"/},{className:"string",begin:/'/,end:/'/},s]}}}());hljs.registerLanguage("c-like",function(){"use strict";return function(e){function t(e){return"(?:"+e+")?"}var n="(decltype\\(auto\\)|"+t("[a-zA-Z_]\\w*::")+"[a-zA-Z_]\\w*"+t("<.*?>")+")",r={className:"keyword",begin:"\\b[a-z\\d_]*_t\\b"},a={className:"string",variants:[{begin:'(u8?|U|L)?"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:"(u8?|U|L)?'(\\\\(x[0-9A-Fa-f]{2}|u[0-9A-Fa-f]{4,8}|[0-7]{3}|\\S)|.)",end:"'",illegal:"."},e.END_SAME_AS_BEGIN({begin:/(?:u8?|U|L)?R"([^()\\ ]{0,16})\(/,end:/\)([^()\\ ]{0,16})"/})]},i={className:"number",variants:[{begin:"\\b(0b[01']+)"},{begin:"(-?)\\b([\\d']+(\\.[\\d']*)?|\\.[\\d']+)(u|U|l|L|ul|UL|f|F|b|B)"},{begin:"(-?)(\\b0[xX][a-fA-F0-9']+|(\\b[\\d']+(\\.[\\d']*)?|\\.[\\d']+)([eE][-+]?[\\d']+)?)"}],relevance:0},s={className:"meta",begin:/#\s*[a-z]+\b/,end:/$/,keywords:{"meta-keyword":"if else elif endif define undef warning error line pragma _Pragma ifdef ifndef include"},contains:[{begin:/\\\n/,relevance:0},e.inherit(a,{className:"meta-string"}),{className:"meta-string",begin:/<.*?>/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},o={className:"title",begin:t("[a-zA-Z_]\\w*::")+e.IDENT_RE,relevance:0},c=t("[a-zA-Z_]\\w*::")+e.IDENT_RE+"\\s*\\(",l={keyword:"int float while private char char8_t char16_t char32_t catch import module export virtual operator sizeof dynamic_cast|10 typedef const_cast|10 const for static_cast|10 union namespace unsigned long volatile static protected bool template mutable if public friend do goto auto void enum else break extern using asm case typeid wchar_t short reinterpret_cast|10 default double register explicit signed typename try this switch continue inline delete alignas alignof constexpr consteval constinit decltype concept co_await co_return co_yield requires noexcept static_assert thread_local restrict final override atomic_bool atomic_char atomic_schar atomic_uchar atomic_short atomic_ushort atomic_int atomic_uint atomic_long atomic_ulong atomic_llong atomic_ullong new throw return and and_eq bitand bitor compl not not_eq or or_eq xor xor_eq",built_in:"std string wstring cin cout cerr clog stdin stdout stderr stringstream istringstream ostringstream auto_ptr deque list queue stack vector map set pair bitset multiset multimap unordered_set unordered_map unordered_multiset unordered_multimap priority_queue make_pair array shared_ptr abort terminate abs acos asin atan2 atan calloc ceil cosh cos exit exp fabs floor fmod fprintf fputs free frexp fscanf future isalnum isalpha iscntrl isdigit isgraph islower isprint ispunct isspace isupper isxdigit tolower toupper labs ldexp log10 log malloc realloc memchr memcmp memcpy memset modf pow printf putchar puts scanf sinh sin snprintf sprintf sqrt sscanf strcat strchr strcmp strcpy strcspn strlen strncat strncmp strncpy strpbrk strrchr strspn strstr tanh tan vfprintf vprintf vsprintf endl initializer_list unique_ptr _Bool complex _Complex imaginary _Imaginary",literal:"true false nullptr NULL"},d=[r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,i,a],_={variants:[{begin:/=/,end:/;/},{begin:/\(/,end:/\)/},{beginKeywords:"new throw return else",end:/;/}],keywords:l,contains:d.concat([{begin:/\(/,end:/\)/,keywords:l,contains:d.concat(["self"]),relevance:0}]),relevance:0},u={className:"function",begin:"("+n+"[\\*&\\s]+)+"+c,returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:l,illegal:/[^\w\s\*&:<>]/,contains:[{begin:"decltype\\(auto\\)",keywords:l,relevance:0},{begin:c,returnBegin:!0,contains:[o],relevance:0},{className:"params",begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r,{begin:/\(/,end:/\)/,keywords:l,relevance:0,contains:["self",e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,i,r]}]},r,e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s]};return{aliases:["c","cc","h","c++","h++","hpp","hh","hxx","cxx"],keywords:l,disableAutodetect:!0,illegal:"",keywords:l,contains:["self",r]},{begin:e.IDENT_RE+"::",keywords:l},{className:"class",beginKeywords:"class struct",end:/[{;:]/,contains:[{begin://,contains:["self"]},e.TITLE_MODE]}]),exports:{preprocessor:s,strings:a,keywords:l}}}}());hljs.registerLanguage("coffeescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={keyword:e.concat(["then","unless","until","loop","by","when","and","or","is","isnt","not"]).filter((e=>n=>!e.includes(n))(["var","const","let","function","static"])).join(" "),literal:n.concat(["yes","no","on","off"]).join(" "),built_in:a.concat(["npm","print"]).join(" ")},i="[A-Za-z$_][0-9A-Za-z$_]*",s={className:"subst",begin:/#\{/,end:/}/,keywords:t},o=[r.BINARY_NUMBER_MODE,r.inherit(r.C_NUMBER_MODE,{starts:{end:"(\\s*/)?",relevance:0}}),{className:"string",variants:[{begin:/'''/,end:/'''/,contains:[r.BACKSLASH_ESCAPE]},{begin:/'/,end:/'/,contains:[r.BACKSLASH_ESCAPE]},{begin:/"""/,end:/"""/,contains:[r.BACKSLASH_ESCAPE,s]},{begin:/"/,end:/"/,contains:[r.BACKSLASH_ESCAPE,s]}]},{className:"regexp",variants:[{begin:"///",end:"///",contains:[s,r.HASH_COMMENT_MODE]},{begin:"//[gim]{0,3}(?=\\W)",relevance:0},{begin:/\/(?![ *]).*?(?![\\]).\/[gim]{0,3}(?=\W)/}]},{begin:"@"+i},{subLanguage:"javascript",excludeBegin:!0,excludeEnd:!0,variants:[{begin:"```",end:"```"},{begin:"`",end:"`"}]}];s.contains=o;var c=r.inherit(r.TITLE_MODE,{begin:i}),l={className:"params",begin:"\\([^\\(]",returnBegin:!0,contains:[{begin:/\(/,end:/\)/,keywords:t,contains:["self"].concat(o)}]};return{name:"CoffeeScript",aliases:["coffee","cson","iced"],keywords:t,illegal:/\/\*/,contains:o.concat([r.COMMENT("###","###"),r.HASH_COMMENT_MODE,{className:"function",begin:"^\\s*"+i+"\\s*=\\s*(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[c,l]},{begin:/[:\(,=]\s*/,relevance:0,contains:[{className:"function",begin:"(\\(.*\\))?\\s*\\B[-=]>",end:"[-=]>",returnBegin:!0,contains:[l]}]},{className:"class",beginKeywords:"class",end:"$",illegal:/[:="\[\]]/,contains:[{beginKeywords:"extends",endsWithParent:!0,illegal:/[:="\[\]]/,contains:[c]},c]},{begin:i+":",end:":",returnBegin:!0,returnEnd:!0,relevance:0}])}}}());hljs.registerLanguage("ruby",function(){"use strict";return function(e){var n="[a-zA-Z_]\\w*[!?=]?|[-+~]\\@|<<|>>|=~|===?|<=>|[<>]=?|\\*\\*|[-/+%^&*~`|]|\\[\\]=?",a={keyword:"and then defined module in return redo if BEGIN retry end for self when next until do begin unless END rescue else break undef not super class case require yield alias while ensure elsif or include attr_reader attr_writer attr_accessor",literal:"true false nil"},s={className:"doctag",begin:"@[A-Za-z]+"},i={begin:"#<",end:">"},r=[e.COMMENT("#","$",{contains:[s]}),e.COMMENT("^\\=begin","^\\=end",{contains:[s],relevance:10}),e.COMMENT("^__END__","\\n$")],c={className:"subst",begin:"#\\{",end:"}",keywords:a},t={className:"string",contains:[e.BACKSLASH_ESCAPE,c],variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/`/,end:/`/},{begin:"%[qQwWx]?\\(",end:"\\)"},{begin:"%[qQwWx]?\\[",end:"\\]"},{begin:"%[qQwWx]?{",end:"}"},{begin:"%[qQwWx]?<",end:">"},{begin:"%[qQwWx]?/",end:"/"},{begin:"%[qQwWx]?%",end:"%"},{begin:"%[qQwWx]?-",end:"-"},{begin:"%[qQwWx]?\\|",end:"\\|"},{begin:/\B\?(\\\d{1,3}|\\x[A-Fa-f0-9]{1,2}|\\u[A-Fa-f0-9]{4}|\\?\S)\b/},{begin:/<<[-~]?'?(\w+)(?:.|\n)*?\n\s*\1\b/,returnBegin:!0,contains:[{begin:/<<[-~]?'?/},e.END_SAME_AS_BEGIN({begin:/(\w+)/,end:/(\w+)/,contains:[e.BACKSLASH_ESCAPE,c]})]}]},b={className:"params",begin:"\\(",end:"\\)",endsParent:!0,keywords:a},d=[t,i,{className:"class",beginKeywords:"class module",end:"$|;",illegal:/=/,contains:[e.inherit(e.TITLE_MODE,{begin:"[A-Za-z_]\\w*(::\\w+)*(\\?|\\!)?"}),{begin:"<\\s*",contains:[{begin:"("+e.IDENT_RE+"::)?"+e.IDENT_RE}]}].concat(r)},{className:"function",beginKeywords:"def",end:"$|;",contains:[e.inherit(e.TITLE_MODE,{begin:n}),b].concat(r)},{begin:e.IDENT_RE+"::"},{className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"(\\!|\\?)?:",relevance:0},{className:"symbol",begin:":(?!\\s)",contains:[t,{begin:n}],relevance:0},{className:"number",begin:"(\\b0[0-7_]+)|(\\b0x[0-9a-fA-F_]+)|(\\b[1-9][0-9_]*(\\.[0-9_]+)?)|[0_]\\b",relevance:0},{begin:"(\\$\\W)|((\\$|\\@\\@?)(\\w+))"},{className:"params",begin:/\|/,end:/\|/,keywords:a},{begin:"("+e.RE_STARTERS_RE+"|unless)\\s*",keywords:"unless",contains:[i,{className:"regexp",contains:[e.BACKSLASH_ESCAPE,c],illegal:/\n/,variants:[{begin:"/",end:"/[a-z]*"},{begin:"%r{",end:"}[a-z]*"},{begin:"%r\\(",end:"\\)[a-z]*"},{begin:"%r!",end:"![a-z]*"},{begin:"%r\\[",end:"\\][a-z]*"}]}].concat(r),relevance:0}].concat(r);c.contains=d,b.contains=d;var g=[{begin:/^\s*=>/,starts:{end:"$",contains:d}},{className:"meta",begin:"^([>?]>|[\\w#]+\\(\\w+\\):\\d+:\\d+>|(\\w+-)?\\d+\\.\\d+\\.\\d(p\\d+)?[^>]+>)",starts:{end:"$",contains:d}}];return{name:"Ruby",aliases:["rb","gemspec","podspec","thor","irb"],keywords:a,illegal:/\/\*/,contains:r.concat(g).concat(d)}}}());hljs.registerLanguage("yaml",function(){"use strict";return function(e){var n="true false yes no null",a="[\\w#;/?:@&=+$,.~*\\'()[\\]]+",s={className:"string",relevance:0,variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/\S+/}],contains:[e.BACKSLASH_ESCAPE,{className:"template-variable",variants:[{begin:"{{",end:"}}"},{begin:"%{",end:"}"}]}]},i=e.inherit(s,{variants:[{begin:/'/,end:/'/},{begin:/"/,end:/"/},{begin:/[^\s,{}[\]]+/}]}),l={end:",",endsWithParent:!0,excludeEnd:!0,contains:[],keywords:n,relevance:0},t={begin:"{",end:"}",contains:[l],illegal:"\\n",relevance:0},g={begin:"\\[",end:"\\]",contains:[l],illegal:"\\n",relevance:0},b=[{className:"attr",variants:[{begin:"\\w[\\w :\\/.-]*:(?=[ \t]|$)"},{begin:'"\\w[\\w :\\/.-]*":(?=[ \t]|$)'},{begin:"'\\w[\\w :\\/.-]*':(?=[ \t]|$)"}]},{className:"meta",begin:"^---s*$",relevance:10},{className:"string",begin:"[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*"},{begin:"<%[%=-]?",end:"[%-]?%>",subLanguage:"ruby",excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:"!\\w+!"+a},{className:"type",begin:"!<"+a+">"},{className:"type",begin:"!"+a},{className:"type",begin:"!!"+a},{className:"meta",begin:"&"+e.UNDERSCORE_IDENT_RE+"$"},{className:"meta",begin:"\\*"+e.UNDERSCORE_IDENT_RE+"$"},{className:"bullet",begin:"\\-(?=[ ]|$)",relevance:0},e.HASH_COMMENT_MODE,{beginKeywords:n,keywords:{literal:n}},{className:"number",begin:"\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b"},{className:"number",begin:e.C_NUMBER_RE+"\\b"},t,g,s],c=[...b];return c.pop(),c.push(i),l.contains=c,{name:"YAML",case_insensitive:!0,aliases:["yml","YAML"],contains:b}}}());hljs.registerLanguage("d",function(){"use strict";return function(e){var a={$pattern:e.UNDERSCORE_IDENT_RE,keyword:"abstract alias align asm assert auto body break byte case cast catch class const continue debug default delete deprecated do else enum export extern final finally for foreach foreach_reverse|10 goto if immutable import in inout int interface invariant is lazy macro mixin module new nothrow out override package pragma private protected public pure ref return scope shared static struct super switch synchronized template this throw try typedef typeid typeof union unittest version void volatile while with __FILE__ __LINE__ __gshared|10 __thread __traits __DATE__ __EOF__ __TIME__ __TIMESTAMP__ __VENDOR__ __VERSION__",built_in:"bool cdouble cent cfloat char creal dchar delegate double dstring float function idouble ifloat ireal long real short string ubyte ucent uint ulong ushort wchar wstring",literal:"false null true"},d="((0|[1-9][\\d_]*)|0[bB][01_]+|0[xX]([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))",n="\\\\(['\"\\?\\\\abfnrtv]|u[\\dA-Fa-f]{4}|[0-7]{1,3}|x[\\dA-Fa-f]{2}|U[\\dA-Fa-f]{8})|&[a-zA-Z\\d]{2,};",t={className:"number",begin:"\\b"+d+"(L|u|U|Lu|LU|uL|UL)?",relevance:0},_={className:"number",begin:"\\b(((0[xX](([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)\\.([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*)|\\.?([\\da-fA-F][\\da-fA-F_]*|_[\\da-fA-F][\\da-fA-F_]*))[pP][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))|((0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(\\.\\d*|([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)))|\\d+\\.(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d)|\\.(0|[1-9][\\d_]*)([eE][+-]?(0|[1-9][\\d_]*|\\d[\\d_]*|[\\d_]+?\\d))?))([fF]|L|i|[fF]i|Li)?|"+d+"(i|[fF]i|Li))",relevance:0},r={className:"string",begin:"'("+n+"|.)",end:"'",illegal:"."},i={className:"string",begin:'"',contains:[{begin:n,relevance:0}],end:'"[cwd]?'},s=e.COMMENT("\\/\\+","\\+\\/",{contains:["self"],relevance:10});return{name:"D",keywords:a,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,s,{className:"string",begin:'x"[\\da-fA-F\\s\\n\\r]*"[cwd]?',relevance:10},i,{className:"string",begin:'[rq]"',end:'"[cwd]?',relevance:5},{className:"string",begin:"`",end:"`[cwd]?"},{className:"string",begin:'q"\\{',end:'\\}"'},_,t,r,{className:"meta",begin:"^#!",end:"$",relevance:5},{className:"meta",begin:"#(line)",end:"$",relevance:5},{className:"keyword",begin:"@[a-zA-Z_][a-zA-Z_\\d]*"}]}}}());hljs.registerLanguage("properties",function(){"use strict";return function(e){var n="[ \\t\\f]*",t="("+n+"[:=]"+n+"|[ \\t\\f]+)",a="([^\\\\:= \\t\\f\\n]|\\\\.)+",s={end:t,relevance:0,starts:{className:"string",end:/$/,relevance:0,contains:[{begin:"\\\\\\n"}]}};return{name:".properties",case_insensitive:!0,illegal:/\S/,contains:[e.COMMENT("^\\s*[!#]","$"),{begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+"+t,returnBegin:!0,contains:[{className:"attr",begin:"([^\\\\\\W:= \\t\\f\\n]|\\\\.)+",endsParent:!0,relevance:0}],starts:s},{begin:a+t,returnBegin:!0,relevance:0,contains:[{className:"meta",begin:a,endsParent:!0,relevance:0}],starts:s},{className:"attr",relevance:0,begin:a+n+"$"}]}}}());hljs.registerLanguage("http",function(){"use strict";return function(e){var n="HTTP/[0-9\\.]+";return{name:"HTTP",aliases:["https"],illegal:"\\S",contains:[{begin:"^"+n,end:"$",contains:[{className:"number",begin:"\\b\\d{3}\\b"}]},{begin:"^[A-Z]+ (.*?) "+n+"$",returnBegin:!0,end:"$",contains:[{className:"string",begin:" ",end:" ",excludeBegin:!0,excludeEnd:!0},{begin:n},{className:"keyword",begin:"[A-Z]+"}]},{className:"attribute",begin:"^\\w",end:": ",excludeEnd:!0,illegal:"\\n|\\s|=",starts:{end:"$",relevance:0}},{begin:"\\n\\n",starts:{subLanguage:[],endsWithParent:!0}}]}}}());hljs.registerLanguage("haskell",function(){"use strict";return function(e){var n={variants:[e.COMMENT("--","$"),e.COMMENT("{-","-}",{contains:["self"]})]},i={className:"meta",begin:"{-#",end:"#-}"},a={className:"meta",begin:"^#",end:"$"},s={className:"type",begin:"\\b[A-Z][\\w']*",relevance:0},l={begin:"\\(",end:"\\)",illegal:'"',contains:[i,a,{className:"type",begin:"\\b[A-Z][\\w]*(\\((\\.\\.|,|\\w+)\\))?"},e.inherit(e.TITLE_MODE,{begin:"[_a-z][\\w']*"}),n]};return{name:"Haskell",aliases:["hs"],keywords:"let in if then else case of where do module import hiding qualified type data newtype deriving class instance as default infix infixl infixr foreign export ccall stdcall cplusplus jvm dotnet safe unsafe family forall mdo proc rec",contains:[{beginKeywords:"module",end:"where",keywords:"module where",contains:[l,n],illegal:"\\W\\.|;"},{begin:"\\bimport\\b",end:"$",keywords:"import qualified as hiding",contains:[l,n],illegal:"\\W\\.|;"},{className:"class",begin:"^(\\s*)?(class|instance)\\b",end:"where",keywords:"class family instance where",contains:[s,l,n]},{className:"class",begin:"\\b(data|(new)?type)\\b",end:"$",keywords:"data family type newtype deriving",contains:[i,s,l,{begin:"{",end:"}",contains:l.contains},n]},{beginKeywords:"default",end:"$",contains:[s,l,n]},{beginKeywords:"infix infixl infixr",end:"$",contains:[e.C_NUMBER_MODE,n]},{begin:"\\bforeign\\b",end:"$",keywords:"foreign import export ccall stdcall cplusplus jvm dotnet safe unsafe",contains:[s,e.QUOTE_STRING_MODE,n]},{className:"meta",begin:"#!\\/usr\\/bin\\/env runhaskell",end:"$"},i,a,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,s,e.inherit(e.TITLE_MODE,{begin:"^[_a-z][\\w']*"}),n,{begin:"->|<-"}]}}}());hljs.registerLanguage("handlebars",function(){"use strict";function e(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(n){const a={"builtin-name":"action bindattr collection component concat debugger each each-in get hash if in input link-to loc log lookup mut outlet partial query-params render template textarea unbound unless view with yield"},t=/\[.*?\]/,s=/[^\s!"#%&'()*+,.\/;<=>@\[\\\]^`{|}~]+/,i=e("(",/'.*?'/,"|",/".*?"/,"|",t,"|",s,"|",/\.|\//,")+"),r=e("(",t,"|",s,")(?==)"),l={begin:i,lexemes:/[\w.\/]+/},c=n.inherit(l,{keywords:{literal:"true false undefined null"}}),o={begin:/\(/,end:/\)/},m={className:"attr",begin:r,relevance:0,starts:{begin:/=/,end:/=/,starts:{contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,c,o]}}},d={contains:[n.NUMBER_MODE,n.QUOTE_STRING_MODE,n.APOS_STRING_MODE,{begin:/as\s+\|/,keywords:{keyword:"as"},end:/\|/,contains:[{begin:/\w+/}]},m,c,o],returnEnd:!0},g=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/\)/})});o.contains=[g];const u=n.inherit(l,{keywords:a,className:"name",starts:n.inherit(d,{end:/}}/})}),b=n.inherit(l,{keywords:a,className:"name"}),h=n.inherit(l,{className:"name",keywords:a,starts:n.inherit(d,{end:/}}/})});return{name:"Handlebars",aliases:["hbs","html.hbs","html.handlebars","htmlbars"],case_insensitive:!0,subLanguage:"xml",contains:[{begin:/\\\{\{/,skip:!0},{begin:/\\\\(?=\{\{)/,skip:!0},n.COMMENT(/\{\{!--/,/--\}\}/),n.COMMENT(/\{\{!/,/\}\}/),{className:"template-tag",begin:/\{\{\{\{(?!\/)/,end:/\}\}\}\}/,contains:[u],starts:{end:/\{\{\{\{\//,returnEnd:!0,subLanguage:"xml"}},{className:"template-tag",begin:/\{\{\{\{\//,end:/\}\}\}\}/,contains:[b]},{className:"template-tag",begin:/\{\{#/,end:/\}\}/,contains:[u]},{className:"template-tag",begin:/\{\{(?=else\}\})/,end:/\}\}/,keywords:"else"},{className:"template-tag",begin:/\{\{\//,end:/\}\}/,contains:[b]},{className:"template-variable",begin:/\{\{\{/,end:/\}\}\}/,contains:[h]},{className:"template-variable",begin:/\{\{/,end:/\}\}/,contains:[h]}]}}}());hljs.registerLanguage("rust",function(){"use strict";return function(e){var n="([ui](8|16|32|64|128|size)|f(32|64))?",t="drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!";return{name:"Rust",aliases:["rs"],keywords:{$pattern:e.IDENT_RE+"!?",keyword:"abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield",literal:"true false Some None Ok Err",built_in:t},illegal:""}]}}}());hljs.registerLanguage("cpp",function(){"use strict";return function(e){var t=e.getLanguage("c-like").rawDefinition();return t.disableAutodetect=!1,t.name="C++",t.aliases=["cc","c++","h++","hpp","hh","hxx","cxx"],t}}());hljs.registerLanguage("ini",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(...n){return n.map(n=>e(n)).join("")}return function(a){var s={className:"number",relevance:0,variants:[{begin:/([\+\-]+)?[\d]+_[\d_]+/},{begin:a.NUMBER_RE}]},i=a.COMMENT();i.variants=[{begin:/;/,end:/$/},{begin:/#/,end:/$/}];var t={className:"variable",variants:[{begin:/\$[\w\d"][\w\d_]*/},{begin:/\$\{(.*?)}/}]},r={className:"literal",begin:/\bon|off|true|false|yes|no\b/},l={className:"string",contains:[a.BACKSLASH_ESCAPE],variants:[{begin:"'''",end:"'''",relevance:10},{begin:'"""',end:'"""',relevance:10},{begin:'"',end:'"'},{begin:"'",end:"'"}]},c={begin:/\[/,end:/\]/,contains:[i,r,t,l,s,"self"],relevance:0},g="("+[/[A-Za-z0-9_-]+/,/"(\\"|[^"])*"/,/'[^']*'/].map(n=>e(n)).join("|")+")";return{name:"TOML, also INI",aliases:["toml"],case_insensitive:!0,illegal:/\S/,contains:[i,{className:"section",begin:/\[+/,end:/\]+/},{begin:n(g,"(\\s*\\.\\s*",g,")*",n("(?=",/\s*=\s*[^#\s]/,")")),className:"attr",starts:{end:/$/,contains:[i,c,r,t,l,s]}}]}}}());hljs.registerLanguage("objectivec",function(){"use strict";return function(e){var n=/[a-zA-Z@][a-zA-Z0-9_]*/,_={$pattern:n,keyword:"@interface @class @protocol @implementation"};return{name:"Objective-C",aliases:["mm","objc","obj-c"],keywords:{$pattern:n,keyword:"int float while char export sizeof typedef const struct for union unsigned long volatile static bool mutable if do return goto void enum else break extern asm case short default double register explicit signed typename this switch continue wchar_t inline readonly assign readwrite self @synchronized id typeof nonatomic super unichar IBOutlet IBAction strong weak copy in out inout bycopy byref oneway __strong __weak __block __autoreleasing @private @protected @public @try @property @end @throw @catch @finally @autoreleasepool @synthesize @dynamic @selector @optional @required @encode @package @import @defs @compatibility_alias __bridge __bridge_transfer __bridge_retained __bridge_retain __covariant __contravariant __kindof _Nonnull _Nullable _Null_unspecified __FUNCTION__ __PRETTY_FUNCTION__ __attribute__ getter setter retain unsafe_unretained nonnull nullable null_unspecified null_resettable class instancetype NS_DESIGNATED_INITIALIZER NS_UNAVAILABLE NS_REQUIRES_SUPER NS_RETURNS_INNER_POINTER NS_INLINE NS_AVAILABLE NS_DEPRECATED NS_ENUM NS_OPTIONS NS_SWIFT_UNAVAILABLE NS_ASSUME_NONNULL_BEGIN NS_ASSUME_NONNULL_END NS_REFINED_FOR_SWIFT NS_SWIFT_NAME NS_SWIFT_NOTHROW NS_DURING NS_HANDLER NS_ENDHANDLER NS_VALUERETURN NS_VOIDRETURN",literal:"false true FALSE TRUE nil YES NO NULL",built_in:"BOOL dispatch_once_t dispatch_queue_t dispatch_sync dispatch_async dispatch_once"},illegal:"/,end:/$/,illegal:"\\n"},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},{className:"class",begin:"("+_.keyword.split(" ").join("|")+")\\b",end:"({|$)",excludeEnd:!0,keywords:_,contains:[e.UNDERSCORE_TITLE_MODE]},{begin:"\\."+e.UNDERSCORE_IDENT_RE,relevance:0}]}}}());hljs.registerLanguage("apache",function(){"use strict";return function(e){var n={className:"number",begin:"\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}\\.\\d{1,3}(:\\d{1,5})?"};return{name:"Apache config",aliases:["apacheconf"],case_insensitive:!0,contains:[e.HASH_COMMENT_MODE,{className:"section",begin:"",contains:[n,{className:"number",begin:":\\d{1,5}"},e.inherit(e.QUOTE_STRING_MODE,{relevance:0})]},{className:"attribute",begin:/\w+/,relevance:0,keywords:{nomarkup:"order deny allow setenv rewriterule rewriteengine rewritecond documentroot sethandler errordocument loadmodule options header listen serverroot servername"},starts:{end:/$/,relevance:0,keywords:{literal:"on off all deny allow"},contains:[{className:"meta",begin:"\\s\\[",end:"\\]$"},{className:"variable",begin:"[\\$%]\\{",end:"\\}",contains:["self",{className:"number",begin:"[\\$%]\\d+"}]},n,{className:"number",begin:"\\d+"},e.QUOTE_STRING_MODE]}}],illegal:/\S/}}}());hljs.registerLanguage("java",function(){"use strict";function e(e){return e?"string"==typeof e?e:e.source:null}function n(e){return a("(",e,")?")}function a(...n){return n.map(n=>e(n)).join("")}function s(...n){return"("+n.map(n=>e(n)).join("|")+")"}return function(e){var t="false synchronized int abstract float private char boolean var static null if const for true while long strictfp finally protected import native final void enum else break transient catch instanceof byte super volatile case assert short package default double public try this switch continue throws protected public private module requires exports do",i={className:"meta",begin:"@[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*",contains:[{begin:/\(/,end:/\)/,contains:["self"]}]},r=e=>a("[",e,"]+([",e,"_]*[",e,"]+)?"),c={className:"number",variants:[{begin:`\\b(0[bB]${r("01")})[lL]?`},{begin:`\\b(0${r("0-7")})[dDfFlL]?`},{begin:a(/\b0[xX]/,s(a(r("a-fA-F0-9"),/\./,r("a-fA-F0-9")),a(r("a-fA-F0-9"),/\.?/),a(/\./,r("a-fA-F0-9"))),/([pP][+-]?(\d+))?/,/[fFdDlL]?/)},{begin:a(/\b/,s(a(/\d*\./,r("\\d")),r("\\d")),/[eE][+-]?[\d]+[dDfF]?/)},{begin:a(/\b/,r(/\d/),n(/\.?/),n(r(/\d/)),/[dDfFlL]?/)}],relevance:0};return{name:"Java",aliases:["jsp"],keywords:t,illegal:/<\/|#/,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{begin:/\w+@/,relevance:0},{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"class",beginKeywords:"class interface",end:/[{;=]/,excludeEnd:!0,keywords:"class interface",illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends implements"},e.UNDERSCORE_TITLE_MODE]},{beginKeywords:"new throw return else",relevance:0},{className:"function",begin:"([À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(<[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*(\\s*,\\s*[À-ʸa-zA-Z_$][À-ʸa-zA-Z_$0-9]*)*>)?\\s+)+"+e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,end:/[{;=]/,excludeEnd:!0,keywords:t,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"params",begin:/\(/,end:/\)/,keywords:t,relevance:0,contains:[i,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE]},e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE]},c,i]}}}());hljs.registerLanguage("x86asm",function(){"use strict";return function(s){return{name:"Intel x86 Assembly",case_insensitive:!0,keywords:{$pattern:"[.%]?"+s.IDENT_RE,keyword:"lock rep repe repz repne repnz xaquire xrelease bnd nobnd aaa aad aam aas adc add and arpl bb0_reset bb1_reset bound bsf bsr bswap bt btc btr bts call cbw cdq cdqe clc cld cli clts cmc cmp cmpsb cmpsd cmpsq cmpsw cmpxchg cmpxchg486 cmpxchg8b cmpxchg16b cpuid cpu_read cpu_write cqo cwd cwde daa das dec div dmint emms enter equ f2xm1 fabs fadd faddp fbld fbstp fchs fclex fcmovb fcmovbe fcmove fcmovnb fcmovnbe fcmovne fcmovnu fcmovu fcom fcomi fcomip fcomp fcompp fcos fdecstp fdisi fdiv fdivp fdivr fdivrp femms feni ffree ffreep fiadd ficom ficomp fidiv fidivr fild fimul fincstp finit fist fistp fisttp fisub fisubr fld fld1 fldcw fldenv fldl2e fldl2t fldlg2 fldln2 fldpi fldz fmul fmulp fnclex fndisi fneni fninit fnop fnsave fnstcw fnstenv fnstsw fpatan fprem fprem1 fptan frndint frstor fsave fscale fsetpm fsin fsincos fsqrt fst fstcw fstenv fstp fstsw fsub fsubp fsubr fsubrp ftst fucom fucomi fucomip fucomp fucompp fxam fxch fxtract fyl2x fyl2xp1 hlt ibts icebp idiv imul in inc incbin insb insd insw int int01 int1 int03 int3 into invd invpcid invlpg invlpga iret iretd iretq iretw jcxz jecxz jrcxz jmp jmpe lahf lar lds lea leave les lfence lfs lgdt lgs lidt lldt lmsw loadall loadall286 lodsb lodsd lodsq lodsw loop loope loopne loopnz loopz lsl lss ltr mfence monitor mov movd movq movsb movsd movsq movsw movsx movsxd movzx mul mwait neg nop not or out outsb outsd outsw packssdw packsswb packuswb paddb paddd paddsb paddsiw paddsw paddusb paddusw paddw pand pandn pause paveb pavgusb pcmpeqb pcmpeqd pcmpeqw pcmpgtb pcmpgtd pcmpgtw pdistib pf2id pfacc pfadd pfcmpeq pfcmpge pfcmpgt pfmax pfmin pfmul pfrcp pfrcpit1 pfrcpit2 pfrsqit1 pfrsqrt pfsub pfsubr pi2fd pmachriw pmaddwd pmagw pmulhriw pmulhrwa pmulhrwc pmulhw pmullw pmvgezb pmvlzb pmvnzb pmvzb pop popa popad popaw popf popfd popfq popfw por prefetch prefetchw pslld psllq psllw psrad psraw psrld psrlq psrlw psubb psubd psubsb psubsiw psubsw psubusb psubusw psubw punpckhbw punpckhdq punpckhwd punpcklbw punpckldq punpcklwd push pusha pushad pushaw pushf pushfd pushfq pushfw pxor rcl rcr rdshr rdmsr rdpmc rdtsc rdtscp ret retf retn rol ror rdm rsdc rsldt rsm rsts sahf sal salc sar sbb scasb scasd scasq scasw sfence sgdt shl shld shr shrd sidt sldt skinit smi smint smintold smsw stc std sti stosb stosd stosq stosw str sub svdc svldt svts swapgs syscall sysenter sysexit sysret test ud0 ud1 ud2b ud2 ud2a umov verr verw fwait wbinvd wrshr wrmsr xadd xbts xchg xlatb xlat xor cmove cmovz cmovne cmovnz cmova cmovnbe cmovae cmovnb cmovb cmovnae cmovbe cmovna cmovg cmovnle cmovge cmovnl cmovl cmovnge cmovle cmovng cmovc cmovnc cmovo cmovno cmovs cmovns cmovp cmovpe cmovnp cmovpo je jz jne jnz ja jnbe jae jnb jb jnae jbe jna jg jnle jge jnl jl jnge jle jng jc jnc jo jno js jns jpo jnp jpe jp sete setz setne setnz seta setnbe setae setnb setnc setb setnae setcset setbe setna setg setnle setge setnl setl setnge setle setng sets setns seto setno setpe setp setpo setnp addps addss andnps andps cmpeqps cmpeqss cmpleps cmpless cmpltps cmpltss cmpneqps cmpneqss cmpnleps cmpnless cmpnltps cmpnltss cmpordps cmpordss cmpunordps cmpunordss cmpps cmpss comiss cvtpi2ps cvtps2pi cvtsi2ss cvtss2si cvttps2pi cvttss2si divps divss ldmxcsr maxps maxss minps minss movaps movhps movlhps movlps movhlps movmskps movntps movss movups mulps mulss orps rcpps rcpss rsqrtps rsqrtss shufps sqrtps sqrtss stmxcsr subps subss ucomiss unpckhps unpcklps xorps fxrstor fxrstor64 fxsave fxsave64 xgetbv xsetbv xsave xsave64 xsaveopt xsaveopt64 xrstor xrstor64 prefetchnta prefetcht0 prefetcht1 prefetcht2 maskmovq movntq pavgb pavgw pextrw pinsrw pmaxsw pmaxub pminsw pminub pmovmskb pmulhuw psadbw pshufw pf2iw pfnacc pfpnacc pi2fw pswapd maskmovdqu clflush movntdq movnti movntpd movdqa movdqu movdq2q movq2dq paddq pmuludq pshufd pshufhw pshuflw pslldq psrldq psubq punpckhqdq punpcklqdq addpd addsd andnpd andpd cmpeqpd cmpeqsd cmplepd cmplesd cmpltpd cmpltsd cmpneqpd cmpneqsd cmpnlepd cmpnlesd cmpnltpd cmpnltsd cmpordpd cmpordsd cmpunordpd cmpunordsd cmppd comisd cvtdq2pd cvtdq2ps cvtpd2dq cvtpd2pi cvtpd2ps cvtpi2pd cvtps2dq cvtps2pd cvtsd2si cvtsd2ss cvtsi2sd cvtss2sd cvttpd2pi cvttpd2dq cvttps2dq cvttsd2si divpd divsd maxpd maxsd minpd minsd movapd movhpd movlpd movmskpd movupd mulpd mulsd orpd shufpd sqrtpd sqrtsd subpd subsd ucomisd unpckhpd unpcklpd xorpd addsubpd addsubps haddpd haddps hsubpd hsubps lddqu movddup movshdup movsldup clgi stgi vmcall vmclear vmfunc vmlaunch vmload vmmcall vmptrld vmptrst vmread vmresume vmrun vmsave vmwrite vmxoff vmxon invept invvpid pabsb pabsw pabsd palignr phaddw phaddd phaddsw phsubw phsubd phsubsw pmaddubsw pmulhrsw pshufb psignb psignw psignd extrq insertq movntsd movntss lzcnt blendpd blendps blendvpd blendvps dppd dpps extractps insertps movntdqa mpsadbw packusdw pblendvb pblendw pcmpeqq pextrb pextrd pextrq phminposuw pinsrb pinsrd pinsrq pmaxsb pmaxsd pmaxud pmaxuw pminsb pminsd pminud pminuw pmovsxbw pmovsxbd pmovsxbq pmovsxwd pmovsxwq pmovsxdq pmovzxbw pmovzxbd pmovzxbq pmovzxwd pmovzxwq pmovzxdq pmuldq pmulld ptest roundpd roundps roundsd roundss crc32 pcmpestri pcmpestrm pcmpistri pcmpistrm pcmpgtq popcnt getsec pfrcpv pfrsqrtv movbe aesenc aesenclast aesdec aesdeclast aesimc aeskeygenassist vaesenc vaesenclast vaesdec vaesdeclast vaesimc vaeskeygenassist vaddpd vaddps vaddsd vaddss vaddsubpd vaddsubps vandpd vandps vandnpd vandnps vblendpd vblendps vblendvpd vblendvps vbroadcastss vbroadcastsd vbroadcastf128 vcmpeq_ospd vcmpeqpd vcmplt_ospd vcmpltpd vcmple_ospd vcmplepd vcmpunord_qpd vcmpunordpd vcmpneq_uqpd vcmpneqpd vcmpnlt_uspd vcmpnltpd vcmpnle_uspd vcmpnlepd vcmpord_qpd vcmpordpd vcmpeq_uqpd vcmpnge_uspd vcmpngepd vcmpngt_uspd vcmpngtpd vcmpfalse_oqpd vcmpfalsepd vcmpneq_oqpd vcmpge_ospd vcmpgepd vcmpgt_ospd vcmpgtpd vcmptrue_uqpd vcmptruepd vcmplt_oqpd vcmple_oqpd vcmpunord_spd vcmpneq_uspd vcmpnlt_uqpd vcmpnle_uqpd vcmpord_spd vcmpeq_uspd vcmpnge_uqpd vcmpngt_uqpd vcmpfalse_ospd vcmpneq_ospd vcmpge_oqpd vcmpgt_oqpd vcmptrue_uspd vcmppd vcmpeq_osps vcmpeqps vcmplt_osps vcmpltps vcmple_osps vcmpleps vcmpunord_qps vcmpunordps vcmpneq_uqps vcmpneqps vcmpnlt_usps vcmpnltps vcmpnle_usps vcmpnleps vcmpord_qps vcmpordps vcmpeq_uqps vcmpnge_usps vcmpngeps vcmpngt_usps vcmpngtps vcmpfalse_oqps vcmpfalseps vcmpneq_oqps vcmpge_osps vcmpgeps vcmpgt_osps vcmpgtps vcmptrue_uqps vcmptrueps vcmplt_oqps vcmple_oqps vcmpunord_sps vcmpneq_usps vcmpnlt_uqps vcmpnle_uqps vcmpord_sps vcmpeq_usps vcmpnge_uqps vcmpngt_uqps vcmpfalse_osps vcmpneq_osps vcmpge_oqps vcmpgt_oqps vcmptrue_usps vcmpps vcmpeq_ossd vcmpeqsd vcmplt_ossd vcmpltsd vcmple_ossd vcmplesd vcmpunord_qsd vcmpunordsd vcmpneq_uqsd vcmpneqsd vcmpnlt_ussd vcmpnltsd vcmpnle_ussd vcmpnlesd vcmpord_qsd vcmpordsd vcmpeq_uqsd vcmpnge_ussd vcmpngesd vcmpngt_ussd vcmpngtsd vcmpfalse_oqsd vcmpfalsesd vcmpneq_oqsd vcmpge_ossd vcmpgesd vcmpgt_ossd vcmpgtsd vcmptrue_uqsd vcmptruesd vcmplt_oqsd vcmple_oqsd vcmpunord_ssd vcmpneq_ussd vcmpnlt_uqsd vcmpnle_uqsd vcmpord_ssd vcmpeq_ussd vcmpnge_uqsd vcmpngt_uqsd vcmpfalse_ossd vcmpneq_ossd vcmpge_oqsd vcmpgt_oqsd vcmptrue_ussd vcmpsd vcmpeq_osss vcmpeqss vcmplt_osss vcmpltss vcmple_osss vcmpless vcmpunord_qss vcmpunordss vcmpneq_uqss vcmpneqss vcmpnlt_usss vcmpnltss vcmpnle_usss vcmpnless vcmpord_qss vcmpordss vcmpeq_uqss vcmpnge_usss vcmpngess vcmpngt_usss vcmpngtss vcmpfalse_oqss vcmpfalsess vcmpneq_oqss vcmpge_osss vcmpgess vcmpgt_osss vcmpgtss vcmptrue_uqss vcmptruess vcmplt_oqss vcmple_oqss vcmpunord_sss vcmpneq_usss vcmpnlt_uqss vcmpnle_uqss vcmpord_sss vcmpeq_usss vcmpnge_uqss vcmpngt_uqss vcmpfalse_osss vcmpneq_osss vcmpge_oqss vcmpgt_oqss vcmptrue_usss vcmpss vcomisd vcomiss vcvtdq2pd vcvtdq2ps vcvtpd2dq vcvtpd2ps vcvtps2dq vcvtps2pd vcvtsd2si vcvtsd2ss vcvtsi2sd vcvtsi2ss vcvtss2sd vcvtss2si vcvttpd2dq vcvttps2dq vcvttsd2si vcvttss2si vdivpd vdivps vdivsd vdivss vdppd vdpps vextractf128 vextractps vhaddpd vhaddps vhsubpd vhsubps vinsertf128 vinsertps vlddqu vldqqu vldmxcsr vmaskmovdqu vmaskmovps vmaskmovpd vmaxpd vmaxps vmaxsd vmaxss vminpd vminps vminsd vminss vmovapd vmovaps vmovd vmovq vmovddup vmovdqa vmovqqa vmovdqu vmovqqu vmovhlps vmovhpd vmovhps vmovlhps vmovlpd vmovlps vmovmskpd vmovmskps vmovntdq vmovntqq vmovntdqa vmovntpd vmovntps vmovsd vmovshdup vmovsldup vmovss vmovupd vmovups vmpsadbw vmulpd vmulps vmulsd vmulss vorpd vorps vpabsb vpabsw vpabsd vpacksswb vpackssdw vpackuswb vpackusdw vpaddb vpaddw vpaddd vpaddq vpaddsb vpaddsw vpaddusb vpaddusw vpalignr vpand vpandn vpavgb vpavgw vpblendvb vpblendw vpcmpestri vpcmpestrm vpcmpistri vpcmpistrm vpcmpeqb vpcmpeqw vpcmpeqd vpcmpeqq vpcmpgtb vpcmpgtw vpcmpgtd vpcmpgtq vpermilpd vpermilps vperm2f128 vpextrb vpextrw vpextrd vpextrq vphaddw vphaddd vphaddsw vphminposuw vphsubw vphsubd vphsubsw vpinsrb vpinsrw vpinsrd vpinsrq vpmaddwd vpmaddubsw vpmaxsb vpmaxsw vpmaxsd vpmaxub vpmaxuw vpmaxud vpminsb vpminsw vpminsd vpminub vpminuw vpminud vpmovmskb vpmovsxbw vpmovsxbd vpmovsxbq vpmovsxwd vpmovsxwq vpmovsxdq vpmovzxbw vpmovzxbd vpmovzxbq vpmovzxwd vpmovzxwq vpmovzxdq vpmulhuw vpmulhrsw vpmulhw vpmullw vpmulld vpmuludq vpmuldq vpor vpsadbw vpshufb vpshufd vpshufhw vpshuflw vpsignb vpsignw vpsignd vpslldq vpsrldq vpsllw vpslld vpsllq vpsraw vpsrad vpsrlw vpsrld vpsrlq vptest vpsubb vpsubw vpsubd vpsubq vpsubsb vpsubsw vpsubusb vpsubusw vpunpckhbw vpunpckhwd vpunpckhdq vpunpckhqdq vpunpcklbw vpunpcklwd vpunpckldq vpunpcklqdq vpxor vrcpps vrcpss vrsqrtps vrsqrtss vroundpd vroundps vroundsd vroundss vshufpd vshufps vsqrtpd vsqrtps vsqrtsd vsqrtss vstmxcsr vsubpd vsubps vsubsd vsubss vtestps vtestpd vucomisd vucomiss vunpckhpd vunpckhps vunpcklpd vunpcklps vxorpd vxorps vzeroall vzeroupper pclmullqlqdq pclmulhqlqdq pclmullqhqdq pclmulhqhqdq pclmulqdq vpclmullqlqdq vpclmulhqlqdq vpclmullqhqdq vpclmulhqhqdq vpclmulqdq vfmadd132ps vfmadd132pd vfmadd312ps vfmadd312pd vfmadd213ps vfmadd213pd vfmadd123ps vfmadd123pd vfmadd231ps vfmadd231pd vfmadd321ps vfmadd321pd vfmaddsub132ps vfmaddsub132pd vfmaddsub312ps vfmaddsub312pd vfmaddsub213ps vfmaddsub213pd vfmaddsub123ps vfmaddsub123pd vfmaddsub231ps vfmaddsub231pd vfmaddsub321ps vfmaddsub321pd vfmsub132ps vfmsub132pd vfmsub312ps vfmsub312pd vfmsub213ps vfmsub213pd vfmsub123ps vfmsub123pd vfmsub231ps vfmsub231pd vfmsub321ps vfmsub321pd vfmsubadd132ps vfmsubadd132pd vfmsubadd312ps vfmsubadd312pd vfmsubadd213ps vfmsubadd213pd vfmsubadd123ps vfmsubadd123pd vfmsubadd231ps vfmsubadd231pd vfmsubadd321ps vfmsubadd321pd vfnmadd132ps vfnmadd132pd vfnmadd312ps vfnmadd312pd vfnmadd213ps vfnmadd213pd vfnmadd123ps vfnmadd123pd vfnmadd231ps vfnmadd231pd vfnmadd321ps vfnmadd321pd vfnmsub132ps vfnmsub132pd vfnmsub312ps vfnmsub312pd vfnmsub213ps vfnmsub213pd vfnmsub123ps vfnmsub123pd vfnmsub231ps vfnmsub231pd vfnmsub321ps vfnmsub321pd vfmadd132ss vfmadd132sd vfmadd312ss vfmadd312sd vfmadd213ss vfmadd213sd vfmadd123ss vfmadd123sd vfmadd231ss vfmadd231sd vfmadd321ss vfmadd321sd vfmsub132ss vfmsub132sd vfmsub312ss vfmsub312sd vfmsub213ss vfmsub213sd vfmsub123ss vfmsub123sd vfmsub231ss vfmsub231sd vfmsub321ss vfmsub321sd vfnmadd132ss vfnmadd132sd vfnmadd312ss vfnmadd312sd vfnmadd213ss vfnmadd213sd vfnmadd123ss vfnmadd123sd vfnmadd231ss vfnmadd231sd vfnmadd321ss vfnmadd321sd vfnmsub132ss vfnmsub132sd vfnmsub312ss vfnmsub312sd vfnmsub213ss vfnmsub213sd vfnmsub123ss vfnmsub123sd vfnmsub231ss vfnmsub231sd vfnmsub321ss vfnmsub321sd rdfsbase rdgsbase rdrand wrfsbase wrgsbase vcvtph2ps vcvtps2ph adcx adox rdseed clac stac xstore xcryptecb xcryptcbc xcryptctr xcryptcfb xcryptofb montmul xsha1 xsha256 llwpcb slwpcb lwpval lwpins vfmaddpd vfmaddps vfmaddsd vfmaddss vfmaddsubpd vfmaddsubps vfmsubaddpd vfmsubaddps vfmsubpd vfmsubps vfmsubsd vfmsubss vfnmaddpd vfnmaddps vfnmaddsd vfnmaddss vfnmsubpd vfnmsubps vfnmsubsd vfnmsubss vfrczpd vfrczps vfrczsd vfrczss vpcmov vpcomb vpcomd vpcomq vpcomub vpcomud vpcomuq vpcomuw vpcomw vphaddbd vphaddbq vphaddbw vphadddq vphaddubd vphaddubq vphaddubw vphaddudq vphadduwd vphadduwq vphaddwd vphaddwq vphsubbw vphsubdq vphsubwd vpmacsdd vpmacsdqh vpmacsdql vpmacssdd vpmacssdqh vpmacssdql vpmacsswd vpmacssww vpmacswd vpmacsww vpmadcsswd vpmadcswd vpperm vprotb vprotd vprotq vprotw vpshab vpshad vpshaq vpshaw vpshlb vpshld vpshlq vpshlw vbroadcasti128 vpblendd vpbroadcastb vpbroadcastw vpbroadcastd vpbroadcastq vpermd vpermpd vpermps vpermq vperm2i128 vextracti128 vinserti128 vpmaskmovd vpmaskmovq vpsllvd vpsllvq vpsravd vpsrlvd vpsrlvq vgatherdpd vgatherqpd vgatherdps vgatherqps vpgatherdd vpgatherqd vpgatherdq vpgatherqq xabort xbegin xend xtest andn bextr blci blcic blsi blsic blcfill blsfill blcmsk blsmsk blsr blcs bzhi mulx pdep pext rorx sarx shlx shrx tzcnt tzmsk t1mskc valignd valignq vblendmpd vblendmps vbroadcastf32x4 vbroadcastf64x4 vbroadcasti32x4 vbroadcasti64x4 vcompresspd vcompressps vcvtpd2udq vcvtps2udq vcvtsd2usi vcvtss2usi vcvttpd2udq vcvttps2udq vcvttsd2usi vcvttss2usi vcvtudq2pd vcvtudq2ps vcvtusi2sd vcvtusi2ss vexpandpd vexpandps vextractf32x4 vextractf64x4 vextracti32x4 vextracti64x4 vfixupimmpd vfixupimmps vfixupimmsd vfixupimmss vgetexppd vgetexpps vgetexpsd vgetexpss vgetmantpd vgetmantps vgetmantsd vgetmantss vinsertf32x4 vinsertf64x4 vinserti32x4 vinserti64x4 vmovdqa32 vmovdqa64 vmovdqu32 vmovdqu64 vpabsq vpandd vpandnd vpandnq vpandq vpblendmd vpblendmq vpcmpltd vpcmpled vpcmpneqd vpcmpnltd vpcmpnled vpcmpd vpcmpltq vpcmpleq vpcmpneqq vpcmpnltq vpcmpnleq vpcmpq vpcmpequd vpcmpltud vpcmpleud vpcmpnequd vpcmpnltud vpcmpnleud vpcmpud vpcmpequq vpcmpltuq vpcmpleuq vpcmpnequq vpcmpnltuq vpcmpnleuq vpcmpuq vpcompressd vpcompressq vpermi2d vpermi2pd vpermi2ps vpermi2q vpermt2d vpermt2pd vpermt2ps vpermt2q vpexpandd vpexpandq vpmaxsq vpmaxuq vpminsq vpminuq vpmovdb vpmovdw vpmovqb vpmovqd vpmovqw vpmovsdb vpmovsdw vpmovsqb vpmovsqd vpmovsqw vpmovusdb vpmovusdw vpmovusqb vpmovusqd vpmovusqw vpord vporq vprold vprolq vprolvd vprolvq vprord vprorq vprorvd vprorvq vpscatterdd vpscatterdq vpscatterqd vpscatterqq vpsraq vpsravq vpternlogd vpternlogq vptestmd vptestmq vptestnmd vptestnmq vpxord vpxorq vrcp14pd vrcp14ps vrcp14sd vrcp14ss vrndscalepd vrndscaleps vrndscalesd vrndscaless vrsqrt14pd vrsqrt14ps vrsqrt14sd vrsqrt14ss vscalefpd vscalefps vscalefsd vscalefss vscatterdpd vscatterdps vscatterqpd vscatterqps vshuff32x4 vshuff64x2 vshufi32x4 vshufi64x2 kandnw kandw kmovw knotw kortestw korw kshiftlw kshiftrw kunpckbw kxnorw kxorw vpbroadcastmb2q vpbroadcastmw2d vpconflictd vpconflictq vplzcntd vplzcntq vexp2pd vexp2ps vrcp28pd vrcp28ps vrcp28sd vrcp28ss vrsqrt28pd vrsqrt28ps vrsqrt28sd vrsqrt28ss vgatherpf0dpd vgatherpf0dps vgatherpf0qpd vgatherpf0qps vgatherpf1dpd vgatherpf1dps vgatherpf1qpd vgatherpf1qps vscatterpf0dpd vscatterpf0dps vscatterpf0qpd vscatterpf0qps vscatterpf1dpd vscatterpf1dps vscatterpf1qpd vscatterpf1qps prefetchwt1 bndmk bndcl bndcu bndcn bndmov bndldx bndstx sha1rnds4 sha1nexte sha1msg1 sha1msg2 sha256rnds2 sha256msg1 sha256msg2 hint_nop0 hint_nop1 hint_nop2 hint_nop3 hint_nop4 hint_nop5 hint_nop6 hint_nop7 hint_nop8 hint_nop9 hint_nop10 hint_nop11 hint_nop12 hint_nop13 hint_nop14 hint_nop15 hint_nop16 hint_nop17 hint_nop18 hint_nop19 hint_nop20 hint_nop21 hint_nop22 hint_nop23 hint_nop24 hint_nop25 hint_nop26 hint_nop27 hint_nop28 hint_nop29 hint_nop30 hint_nop31 hint_nop32 hint_nop33 hint_nop34 hint_nop35 hint_nop36 hint_nop37 hint_nop38 hint_nop39 hint_nop40 hint_nop41 hint_nop42 hint_nop43 hint_nop44 hint_nop45 hint_nop46 hint_nop47 hint_nop48 hint_nop49 hint_nop50 hint_nop51 hint_nop52 hint_nop53 hint_nop54 hint_nop55 hint_nop56 hint_nop57 hint_nop58 hint_nop59 hint_nop60 hint_nop61 hint_nop62 hint_nop63",built_in:"ip eip rip al ah bl bh cl ch dl dh sil dil bpl spl r8b r9b r10b r11b r12b r13b r14b r15b ax bx cx dx si di bp sp r8w r9w r10w r11w r12w r13w r14w r15w eax ebx ecx edx esi edi ebp esp eip r8d r9d r10d r11d r12d r13d r14d r15d rax rbx rcx rdx rsi rdi rbp rsp r8 r9 r10 r11 r12 r13 r14 r15 cs ds es fs gs ss st st0 st1 st2 st3 st4 st5 st6 st7 mm0 mm1 mm2 mm3 mm4 mm5 mm6 mm7 xmm0 xmm1 xmm2 xmm3 xmm4 xmm5 xmm6 xmm7 xmm8 xmm9 xmm10 xmm11 xmm12 xmm13 xmm14 xmm15 xmm16 xmm17 xmm18 xmm19 xmm20 xmm21 xmm22 xmm23 xmm24 xmm25 xmm26 xmm27 xmm28 xmm29 xmm30 xmm31 ymm0 ymm1 ymm2 ymm3 ymm4 ymm5 ymm6 ymm7 ymm8 ymm9 ymm10 ymm11 ymm12 ymm13 ymm14 ymm15 ymm16 ymm17 ymm18 ymm19 ymm20 ymm21 ymm22 ymm23 ymm24 ymm25 ymm26 ymm27 ymm28 ymm29 ymm30 ymm31 zmm0 zmm1 zmm2 zmm3 zmm4 zmm5 zmm6 zmm7 zmm8 zmm9 zmm10 zmm11 zmm12 zmm13 zmm14 zmm15 zmm16 zmm17 zmm18 zmm19 zmm20 zmm21 zmm22 zmm23 zmm24 zmm25 zmm26 zmm27 zmm28 zmm29 zmm30 zmm31 k0 k1 k2 k3 k4 k5 k6 k7 bnd0 bnd1 bnd2 bnd3 cr0 cr1 cr2 cr3 cr4 cr8 dr0 dr1 dr2 dr3 dr8 tr3 tr4 tr5 tr6 tr7 r0 r1 r2 r3 r4 r5 r6 r7 r0b r1b r2b r3b r4b r5b r6b r7b r0w r1w r2w r3w r4w r5w r6w r7w r0d r1d r2d r3d r4d r5d r6d r7d r0h r1h r2h r3h r0l r1l r2l r3l r4l r5l r6l r7l r8l r9l r10l r11l r12l r13l r14l r15l db dw dd dq dt ddq do dy dz resb resw resd resq rest resdq reso resy resz incbin equ times byte word dword qword nosplit rel abs seg wrt strict near far a32 ptr",meta:"%define %xdefine %+ %undef %defstr %deftok %assign %strcat %strlen %substr %rotate %elif %else %endif %if %ifmacro %ifctx %ifidn %ifidni %ifid %ifnum %ifstr %iftoken %ifempty %ifenv %error %warning %fatal %rep %endrep %include %push %pop %repl %pathsearch %depend %use %arg %stacksize %local %line %comment %endcomment .nolist __FILE__ __LINE__ __SECT__ __BITS__ __OUTPUT_FORMAT__ __DATE__ __TIME__ __DATE_NUM__ __TIME_NUM__ __UTC_DATE__ __UTC_TIME__ __UTC_DATE_NUM__ __UTC_TIME_NUM__ __PASS__ struc endstruc istruc at iend align alignb sectalign daz nodaz up down zero default option assume public bits use16 use32 use64 default section segment absolute extern global common cpu float __utf16__ __utf16le__ __utf16be__ __utf32__ __utf32le__ __utf32be__ __float8__ __float16__ __float32__ __float64__ __float80m__ __float80e__ __float128l__ __float128h__ __Infinity__ __QNaN__ __SNaN__ Inf NaN QNaN SNaN float8 float16 float32 float64 float80m float80e float128l float128h __FLOAT_DAZ__ __FLOAT_ROUND__ __FLOAT__"},contains:[s.COMMENT(";","$",{relevance:0}),{className:"number",variants:[{begin:"\\b(?:([0-9][0-9_]*)?\\.[0-9_]*(?:[eE][+-]?[0-9_]+)?|(0[Xx])?[0-9][0-9_]*\\.?[0-9_]*(?:[pP](?:[+-]?[0-9_]+)?)?)\\b",relevance:0},{begin:"\\$[0-9][0-9A-Fa-f]*",relevance:0},{begin:"\\b(?:[0-9A-Fa-f][0-9A-Fa-f_]*[Hh]|[0-9][0-9_]*[DdTt]?|[0-7][0-7_]*[QqOo]|[0-1][0-1_]*[BbYy])\\b"},{begin:"\\b(?:0[Xx][0-9A-Fa-f_]+|0[DdTt][0-9_]+|0[QqOo][0-7_]+|0[BbYy][0-1_]+)\\b"}]},s.QUOTE_STRING_MODE,{className:"string",variants:[{begin:"'",end:"[^\\\\]'"},{begin:"`",end:"[^\\\\]`"}],relevance:0},{className:"symbol",variants:[{begin:"^\\s*[A-Za-z._?][A-Za-z0-9_$#@~.?]*(:|\\s+label)"},{begin:"^\\s*%%[A-Za-z0-9_$#@~.?]*:"}],relevance:0},{className:"subst",begin:"%[0-9]+",relevance:0},{className:"subst",begin:"%!S+",relevance:0},{className:"meta",begin:/^\s*\.[\w_-]+/}]}}}());hljs.registerLanguage("kotlin",function(){"use strict";return function(e){var n={keyword:"abstract as val var vararg get set class object open private protected public noinline crossinline dynamic final enum if else do while for when throw try catch finally import package is in fun override companion reified inline lateinit init interface annotation data sealed internal infix operator out by constructor super tailrec where const inner suspend typealias external expect actual trait volatile transient native default",built_in:"Byte Short Char Int Long Boolean Float Double Void Unit Nothing",literal:"true false null"},a={className:"symbol",begin:e.UNDERSCORE_IDENT_RE+"@"},i={className:"subst",begin:"\\${",end:"}",contains:[e.C_NUMBER_MODE]},s={className:"variable",begin:"\\$"+e.UNDERSCORE_IDENT_RE},t={className:"string",variants:[{begin:'"""',end:'"""(?=[^"])',contains:[s,i]},{begin:"'",end:"'",illegal:/\n/,contains:[e.BACKSLASH_ESCAPE]},{begin:'"',end:'"',illegal:/\n/,contains:[e.BACKSLASH_ESCAPE,s,i]}]};i.contains.push(t);var r={className:"meta",begin:"@(?:file|property|field|get|set|receiver|param|setparam|delegate)\\s*:(?:\\s*"+e.UNDERSCORE_IDENT_RE+")?"},l={className:"meta",begin:"@"+e.UNDERSCORE_IDENT_RE,contains:[{begin:/\(/,end:/\)/,contains:[e.inherit(t,{className:"meta-string"})]}]},c=e.COMMENT("/\\*","\\*/",{contains:[e.C_BLOCK_COMMENT_MODE]}),o={variants:[{className:"type",begin:e.UNDERSCORE_IDENT_RE},{begin:/\(/,end:/\)/,contains:[]}]},d=o;return d.variants[1].contains=[o],o.variants[1].contains=[d],{name:"Kotlin",aliases:["kt"],keywords:n,contains:[e.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+"}]}),e.C_LINE_COMMENT_MODE,c,{className:"keyword",begin:/\b(break|continue|return|this)\b/,starts:{contains:[{className:"symbol",begin:/@\w+/}]}},a,r,l,{className:"function",beginKeywords:"fun",end:"[(]|$",returnBegin:!0,excludeEnd:!0,keywords:n,illegal:/fun\s+(<.*>)?[^\s\(]+(\s+[^\s\(]+)\s*=/,relevance:5,contains:[{begin:e.UNDERSCORE_IDENT_RE+"\\s*\\(",returnBegin:!0,relevance:0,contains:[e.UNDERSCORE_TITLE_MODE]},{className:"type",begin://,keywords:"reified",relevance:0},{className:"params",begin:/\(/,end:/\)/,endsParent:!0,keywords:n,relevance:0,contains:[{begin:/:/,end:/[=,\/]/,endsWithParent:!0,contains:[o,e.C_LINE_COMMENT_MODE,c],relevance:0},e.C_LINE_COMMENT_MODE,c,r,l,t,e.C_NUMBER_MODE]},c]},{className:"class",beginKeywords:"class interface trait",end:/[:\{(]|$/,excludeEnd:!0,illegal:"extends implements",contains:[{beginKeywords:"public protected internal private constructor"},e.UNDERSCORE_TITLE_MODE,{className:"type",begin://,excludeBegin:!0,excludeEnd:!0,relevance:0},{className:"type",begin:/[,:]\s*/,end:/[<\(,]|$/,excludeBegin:!0,returnEnd:!0},r,l]},t,{className:"meta",begin:"^#!/usr/bin/env",end:"$",illegal:"\n"},{className:"number",begin:"\\b(0[bB]([01]+[01_]+[01]+|[01]+)|0[xX]([a-fA-F0-9]+[a-fA-F0-9_]+[a-fA-F0-9]+|[a-fA-F0-9]+)|(([\\d]+[\\d_]+[\\d]+|[\\d]+)(\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))?|\\.([\\d]+[\\d_]+[\\d]+|[\\d]+))([eE][-+]?\\d+)?)[lLfF]?",relevance:0}]}}}());hljs.registerLanguage("armasm",function(){"use strict";return function(s){const e={variants:[s.COMMENT("^[ \\t]*(?=#)","$",{relevance:0,excludeBegin:!0}),s.COMMENT("[;@]","$",{relevance:0}),s.C_LINE_COMMENT_MODE,s.C_BLOCK_COMMENT_MODE]};return{name:"ARM Assembly",case_insensitive:!0,aliases:["arm"],keywords:{$pattern:"\\.?"+s.IDENT_RE,meta:".2byte .4byte .align .ascii .asciz .balign .byte .code .data .else .end .endif .endm .endr .equ .err .exitm .extern .global .hword .if .ifdef .ifndef .include .irp .long .macro .rept .req .section .set .skip .space .text .word .arm .thumb .code16 .code32 .force_thumb .thumb_func .ltorg ALIAS ALIGN ARM AREA ASSERT ATTR CN CODE CODE16 CODE32 COMMON CP DATA DCB DCD DCDU DCDO DCFD DCFDU DCI DCQ DCQU DCW DCWU DN ELIF ELSE END ENDFUNC ENDIF ENDP ENTRY EQU EXPORT EXPORTAS EXTERN FIELD FILL FUNCTION GBLA GBLL GBLS GET GLOBAL IF IMPORT INCBIN INCLUDE INFO KEEP LCLA LCLL LCLS LTORG MACRO MAP MEND MEXIT NOFP OPT PRESERVE8 PROC QN READONLY RELOC REQUIRE REQUIRE8 RLIST FN ROUT SETA SETL SETS SN SPACE SUBT THUMB THUMBX TTL WHILE WEND ",built_in:"r0 r1 r2 r3 r4 r5 r6 r7 r8 r9 r10 r11 r12 r13 r14 r15 pc lr sp ip sl sb fp a1 a2 a3 a4 v1 v2 v3 v4 v5 v6 v7 v8 f0 f1 f2 f3 f4 f5 f6 f7 p0 p1 p2 p3 p4 p5 p6 p7 p8 p9 p10 p11 p12 p13 p14 p15 c0 c1 c2 c3 c4 c5 c6 c7 c8 c9 c10 c11 c12 c13 c14 c15 q0 q1 q2 q3 q4 q5 q6 q7 q8 q9 q10 q11 q12 q13 q14 q15 cpsr_c cpsr_x cpsr_s cpsr_f cpsr_cx cpsr_cxs cpsr_xs cpsr_xsf cpsr_sf cpsr_cxsf spsr_c spsr_x spsr_s spsr_f spsr_cx spsr_cxs spsr_xs spsr_xsf spsr_sf spsr_cxsf s0 s1 s2 s3 s4 s5 s6 s7 s8 s9 s10 s11 s12 s13 s14 s15 s16 s17 s18 s19 s20 s21 s22 s23 s24 s25 s26 s27 s28 s29 s30 s31 d0 d1 d2 d3 d4 d5 d6 d7 d8 d9 d10 d11 d12 d13 d14 d15 d16 d17 d18 d19 d20 d21 d22 d23 d24 d25 d26 d27 d28 d29 d30 d31 {PC} {VAR} {TRUE} {FALSE} {OPT} {CONFIG} {ENDIAN} {CODESIZE} {CPU} {FPU} {ARCHITECTURE} {PCSTOREOFFSET} {ARMASM_VERSION} {INTER} {ROPI} {RWPI} {SWST} {NOSWST} . @"},contains:[{className:"keyword",begin:"\\b(adc|(qd?|sh?|u[qh]?)?add(8|16)?|usada?8|(q|sh?|u[qh]?)?(as|sa)x|and|adrl?|sbc|rs[bc]|asr|b[lx]?|blx|bxj|cbn?z|tb[bh]|bic|bfc|bfi|[su]bfx|bkpt|cdp2?|clz|clrex|cmp|cmn|cpsi[ed]|cps|setend|dbg|dmb|dsb|eor|isb|it[te]{0,3}|lsl|lsr|ror|rrx|ldm(([id][ab])|f[ds])?|ldr((s|ex)?[bhd])?|movt?|mvn|mra|mar|mul|[us]mull|smul[bwt][bt]|smu[as]d|smmul|smmla|mla|umlaal|smlal?([wbt][bt]|d)|mls|smlsl?[ds]|smc|svc|sev|mia([bt]{2}|ph)?|mrr?c2?|mcrr2?|mrs|msr|orr|orn|pkh(tb|bt)|rbit|rev(16|sh)?|sel|[su]sat(16)?|nop|pop|push|rfe([id][ab])?|stm([id][ab])?|str(ex)?[bhd]?|(qd?)?sub|(sh?|q|u[qh]?)?sub(8|16)|[su]xt(a?h|a?b(16)?)|srs([id][ab])?|swpb?|swi|smi|tst|teq|wfe|wfi|yield)(eq|ne|cs|cc|mi|pl|vs|vc|hi|ls|ge|lt|gt|le|al|hs|lo)?[sptrx]?(?=\\s)"},e,s.QUOTE_STRING_MODE,{className:"string",begin:"'",end:"[^\\\\]'",relevance:0},{className:"title",begin:"\\|",end:"\\|",illegal:"\\n",relevance:0},{className:"number",variants:[{begin:"[#$=]?0x[0-9a-f]+"},{begin:"[#$=]?0b[01]+"},{begin:"[#$=]\\d+"},{begin:"\\b\\d+"}],relevance:0},{className:"symbol",variants:[{begin:"^[ \\t]*[a-z_\\.\\$][a-z0-9_\\.\\$]+:"},{begin:"^[a-z_\\.\\$][a-z0-9_\\.\\$]+"},{begin:"[=#]\\w+"}],relevance:0}]}}}());hljs.registerLanguage("go",function(){"use strict";return function(e){var n={keyword:"break default func interface select case map struct chan else goto package switch const fallthrough if range type continue for import return var go defer bool byte complex64 complex128 float32 float64 int8 int16 int32 int64 string uint8 uint16 uint32 uint64 int uint uintptr rune",literal:"true false iota nil",built_in:"append cap close complex copy imag len make new panic print println real recover delete"};return{name:"Go",aliases:["golang"],keywords:n,illegal:">>|\.\.\.) /},i={className:"subst",begin:/\{/,end:/\}/,keywords:n,illegal:/#/},s={begin:/\{\{/,relevance:0},r={className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:/(u|b)?r?'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(u|b)?r?"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a],relevance:10},{begin:/(fr|rf|f)'''/,end:/'''/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(fr|rf|f)"""/,end:/"""/,contains:[e.BACKSLASH_ESCAPE,a,s,i]},{begin:/(u|r|ur)'/,end:/'/,relevance:10},{begin:/(u|r|ur)"/,end:/"/,relevance:10},{begin:/(b|br)'/,end:/'/},{begin:/(b|br)"/,end:/"/},{begin:/(fr|rf|f)'/,end:/'/,contains:[e.BACKSLASH_ESCAPE,s,i]},{begin:/(fr|rf|f)"/,end:/"/,contains:[e.BACKSLASH_ESCAPE,s,i]},e.APOS_STRING_MODE,e.QUOTE_STRING_MODE]},l={className:"number",relevance:0,variants:[{begin:e.BINARY_NUMBER_RE+"[lLjJ]?"},{begin:"\\b(0o[0-7]+)[lLjJ]?"},{begin:e.C_NUMBER_RE+"[lLjJ]?"}]},t={className:"params",variants:[{begin:/\(\s*\)/,skip:!0,className:null},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:["self",a,l,r,e.HASH_COMMENT_MODE]}]};return i.contains=[r,l,a],{name:"Python",aliases:["py","gyp","ipython"],keywords:n,illegal:/(<\/|->|\?)|=>/,contains:[a,l,{beginKeywords:"if",relevance:0},r,e.HASH_COMMENT_MODE,{variants:[{className:"function",beginKeywords:"def"},{className:"class",beginKeywords:"class"}],end:/:/,illegal:/[${=;\n,]/,contains:[e.UNDERSCORE_TITLE_MODE,t,{begin:/->/,endsWithParent:!0,keywords:"None"}]},{className:"meta",begin:/^[\t ]*@/,end:/$/},{begin:/\b(print|exec)\(/}]}}}());hljs.registerLanguage("shell",function(){"use strict";return function(s){return{name:"Shell Session",aliases:["console"],contains:[{className:"meta",begin:"^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]",starts:{end:"$",subLanguage:"bash"}}]}}}());hljs.registerLanguage("scala",function(){"use strict";return function(e){var n={className:"subst",variants:[{begin:"\\$[A-Za-z0-9_]+"},{begin:"\\${",end:"}"}]},a={className:"string",variants:[{begin:'"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE]},{begin:'"""',end:'"""',relevance:10},{begin:'[a-z]+"',end:'"',illegal:"\\n",contains:[e.BACKSLASH_ESCAPE,n]},{className:"string",begin:'[a-z]+"""',end:'"""',contains:[n],relevance:10}]},s={className:"type",begin:"\\b[A-Z][A-Za-z0-9_]*",relevance:0},t={className:"title",begin:/[^0-9\n\t "'(),.`{}\[\]:;][^\n\t "'(),.`{}\[\]:;]+|[^0-9\n\t "'(),.`{}\[\]:;=]/,relevance:0},i={className:"class",beginKeywords:"class object trait type",end:/[:={\[\n;]/,excludeEnd:!0,contains:[{beginKeywords:"extends with",relevance:10},{begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},{className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,relevance:0,contains:[s]},t]},l={className:"function",beginKeywords:"def",end:/[:={\[(\n;]/,excludeEnd:!0,contains:[t]};return{name:"Scala",keywords:{literal:"true false null",keyword:"type yield lazy override def with val var sealed abstract private trait object if forSome for while throw finally protected extends import final return else break new catch super class case package default try this match continue throws implicit"},contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,a,{className:"symbol",begin:"'\\w[\\w\\d_]*(?!')"},s,l,i,e.C_NUMBER_MODE,{className:"meta",begin:"@[A-Za-z]+"}]}}}());hljs.registerLanguage("julia",function(){"use strict";return function(e){var r="[A-Za-z_\\u00A1-\\uFFFF][A-Za-z_0-9\\u00A1-\\uFFFF]*",t={$pattern:r,keyword:"in isa where baremodule begin break catch ccall const continue do else elseif end export false finally for function global if import importall let local macro module quote return true try using while type immutable abstract bitstype typealias ",literal:"true false ARGS C_NULL DevNull ENDIAN_BOM ENV I Inf Inf16 Inf32 Inf64 InsertionSort JULIA_HOME LOAD_PATH MergeSort NaN NaN16 NaN32 NaN64 PROGRAM_FILE QuickSort RoundDown RoundFromZero RoundNearest RoundNearestTiesAway RoundNearestTiesUp RoundToZero RoundUp STDERR STDIN STDOUT VERSION catalan e|0 eu|0 eulergamma golden im nothing pi γ π φ ",built_in:"ANY AbstractArray AbstractChannel AbstractFloat AbstractMatrix AbstractRNG AbstractSerializer AbstractSet AbstractSparseArray AbstractSparseMatrix AbstractSparseVector AbstractString AbstractUnitRange AbstractVecOrMat AbstractVector Any ArgumentError Array AssertionError Associative Base64DecodePipe Base64EncodePipe Bidiagonal BigFloat BigInt BitArray BitMatrix BitVector Bool BoundsError BufferStream CachingPool CapturedException CartesianIndex CartesianRange Cchar Cdouble Cfloat Channel Char Cint Cintmax_t Clong Clonglong ClusterManager Cmd CodeInfo Colon Complex Complex128 Complex32 Complex64 CompositeException Condition ConjArray ConjMatrix ConjVector Cptrdiff_t Cshort Csize_t Cssize_t Cstring Cuchar Cuint Cuintmax_t Culong Culonglong Cushort Cwchar_t Cwstring DataType Date DateFormat DateTime DenseArray DenseMatrix DenseVecOrMat DenseVector Diagonal Dict DimensionMismatch Dims DirectIndexString Display DivideError DomainError EOFError EachLine Enum Enumerate ErrorException Exception ExponentialBackOff Expr Factorization FileMonitor Float16 Float32 Float64 Function Future GlobalRef GotoNode HTML Hermitian IO IOBuffer IOContext IOStream IPAddr IPv4 IPv6 IndexCartesian IndexLinear IndexStyle InexactError InitError Int Int128 Int16 Int32 Int64 Int8 IntSet Integer InterruptException InvalidStateException Irrational KeyError LabelNode LinSpace LineNumberNode LoadError LowerTriangular MIME Matrix MersenneTwister Method MethodError MethodTable Module NTuple NewvarNode NullException Nullable Number ObjectIdDict OrdinalRange OutOfMemoryError OverflowError Pair ParseError PartialQuickSort PermutedDimsArray Pipe PollingFileWatcher ProcessExitedException Ptr QuoteNode RandomDevice Range RangeIndex Rational RawFD ReadOnlyMemoryError Real ReentrantLock Ref Regex RegexMatch RemoteChannel RemoteException RevString RoundingMode RowVector SSAValue SegmentationFault SerializationState Set SharedArray SharedMatrix SharedVector Signed SimpleVector Slot SlotNumber SparseMatrixCSC SparseVector StackFrame StackOverflowError StackTrace StepRange StepRangeLen StridedArray StridedMatrix StridedVecOrMat StridedVector String SubArray SubString SymTridiagonal Symbol Symmetric SystemError TCPSocket Task Text TextDisplay Timer Tridiagonal Tuple Type TypeError TypeMapEntry TypeMapLevel TypeName TypeVar TypedSlot UDPSocket UInt UInt128 UInt16 UInt32 UInt64 UInt8 UndefRefError UndefVarError UnicodeError UniformScaling Union UnionAll UnitRange Unsigned UpperTriangular Val Vararg VecElement VecOrMat Vector VersionNumber Void WeakKeyDict WeakRef WorkerConfig WorkerPool "},a={keywords:t,illegal:/<\//},n={className:"subst",begin:/\$\(/,end:/\)/,keywords:t},o={className:"variable",begin:"\\$"+r},i={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],variants:[{begin:/\w*"""/,end:/"""\w*/,relevance:10},{begin:/\w*"/,end:/"\w*/}]},l={className:"string",contains:[e.BACKSLASH_ESCAPE,n,o],begin:"`",end:"`"},s={className:"meta",begin:"@"+r};return a.name="Julia",a.contains=[{className:"number",begin:/(\b0x[\d_]*(\.[\d_]*)?|0x\.\d[\d_]*)p[-+]?\d+|\b0[box][a-fA-F0-9][a-fA-F0-9_]*|(\b\d[\d_]*(\.[\d_]*)?|\.\d[\d_]*)([eEfF][-+]?\d+)?/,relevance:0},{className:"string",begin:/'(.|\\[xXuU][a-zA-Z0-9]+)'/},i,l,s,{className:"comment",variants:[{begin:"#=",end:"=#",relevance:10},{begin:"#",end:"$"}]},e.HASH_COMMENT_MODE,{className:"keyword",begin:"\\b(((abstract|primitive)\\s+)type|(mutable\\s+)?struct)\\b"},{begin:/<:/}],n.contains=a.contains,a}}());hljs.registerLanguage("php-template",function(){"use strict";return function(n){return{name:"PHP template",subLanguage:"xml",contains:[{begin:/<\?(php|=)?/,end:/\?>/,subLanguage:"php",contains:[{begin:"/\\*",end:"\\*/",skip:!0},{begin:'b"',end:'"',skip:!0},{begin:"b'",end:"'",skip:!0},n.inherit(n.APOS_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0}),n.inherit(n.QUOTE_STRING_MODE,{illegal:null,className:null,contains:null,skip:!0})]}]}}}());hljs.registerLanguage("scss",function(){"use strict";return function(e){var t={className:"variable",begin:"(\\$[a-zA-Z-][a-zA-Z0-9_-]*)\\b"},i={className:"number",begin:"#[0-9A-Fa-f]+"};return e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,e.C_BLOCK_COMMENT_MODE,{name:"SCSS",case_insensitive:!0,illegal:"[=/|']",contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,{className:"selector-id",begin:"\\#[A-Za-z0-9_-]+",relevance:0},{className:"selector-class",begin:"\\.[A-Za-z0-9_-]+",relevance:0},{className:"selector-attr",begin:"\\[",end:"\\]",illegal:"$"},{className:"selector-tag",begin:"\\b(a|abbr|acronym|address|area|article|aside|audio|b|base|big|blockquote|body|br|button|canvas|caption|cite|code|col|colgroup|command|datalist|dd|del|details|dfn|div|dl|dt|em|embed|fieldset|figcaption|figure|footer|form|frame|frameset|(h[1-6])|head|header|hgroup|hr|html|i|iframe|img|input|ins|kbd|keygen|label|legend|li|link|map|mark|meta|meter|nav|noframes|noscript|object|ol|optgroup|option|output|p|param|pre|progress|q|rp|rt|ruby|samp|script|section|select|small|span|strike|strong|style|sub|sup|table|tbody|td|textarea|tfoot|th|thead|time|title|tr|tt|ul|var|video)\\b",relevance:0},{className:"selector-pseudo",begin:":(visited|valid|root|right|required|read-write|read-only|out-range|optional|only-of-type|only-child|nth-of-type|nth-last-of-type|nth-last-child|nth-child|not|link|left|last-of-type|last-child|lang|invalid|indeterminate|in-range|hover|focus|first-of-type|first-line|first-letter|first-child|first|enabled|empty|disabled|default|checked|before|after|active)"},{className:"selector-pseudo",begin:"::(after|before|choices|first-letter|first-line|repeat-index|repeat-item|selection|value)"},t,{className:"attribute",begin:"\\b(src|z-index|word-wrap|word-spacing|word-break|width|widows|white-space|visibility|vertical-align|unicode-bidi|transition-timing-function|transition-property|transition-duration|transition-delay|transition|transform-style|transform-origin|transform|top|text-underline-position|text-transform|text-shadow|text-rendering|text-overflow|text-indent|text-decoration-style|text-decoration-line|text-decoration-color|text-decoration|text-align-last|text-align|tab-size|table-layout|right|resize|quotes|position|pointer-events|perspective-origin|perspective|page-break-inside|page-break-before|page-break-after|padding-top|padding-right|padding-left|padding-bottom|padding|overflow-y|overflow-x|overflow-wrap|overflow|outline-width|outline-style|outline-offset|outline-color|outline|orphans|order|opacity|object-position|object-fit|normal|none|nav-up|nav-right|nav-left|nav-index|nav-down|min-width|min-height|max-width|max-height|mask|marks|margin-top|margin-right|margin-left|margin-bottom|margin|list-style-type|list-style-position|list-style-image|list-style|line-height|letter-spacing|left|justify-content|initial|inherit|ime-mode|image-orientation|image-resolution|image-rendering|icon|hyphens|height|font-weight|font-variant-ligatures|font-variant|font-style|font-stretch|font-size-adjust|font-size|font-language-override|font-kerning|font-feature-settings|font-family|font|float|flex-wrap|flex-shrink|flex-grow|flex-flow|flex-direction|flex-basis|flex|filter|empty-cells|display|direction|cursor|counter-reset|counter-increment|content|column-width|column-span|column-rule-width|column-rule-style|column-rule-color|column-rule|column-gap|column-fill|column-count|columns|color|clip-path|clip|clear|caption-side|break-inside|break-before|break-after|box-sizing|box-shadow|box-decoration-break|bottom|border-width|border-top-width|border-top-style|border-top-right-radius|border-top-left-radius|border-top-color|border-top|border-style|border-spacing|border-right-width|border-right-style|border-right-color|border-right|border-radius|border-left-width|border-left-style|border-left-color|border-left|border-image-width|border-image-source|border-image-slice|border-image-repeat|border-image-outset|border-image|border-color|border-collapse|border-bottom-width|border-bottom-style|border-bottom-right-radius|border-bottom-left-radius|border-bottom-color|border-bottom|border|background-size|background-repeat|background-position|background-origin|background-image|background-color|background-clip|background-attachment|background-blend-mode|background|backface-visibility|auto|animation-timing-function|animation-play-state|animation-name|animation-iteration-count|animation-fill-mode|animation-duration|animation-direction|animation-delay|animation|align-self|align-items|align-content)\\b",illegal:"[^\\s]"},{begin:"\\b(whitespace|wait|w-resize|visible|vertical-text|vertical-ideographic|uppercase|upper-roman|upper-alpha|underline|transparent|top|thin|thick|text|text-top|text-bottom|tb-rl|table-header-group|table-footer-group|sw-resize|super|strict|static|square|solid|small-caps|separate|se-resize|scroll|s-resize|rtl|row-resize|ridge|right|repeat|repeat-y|repeat-x|relative|progress|pointer|overline|outside|outset|oblique|nowrap|not-allowed|normal|none|nw-resize|no-repeat|no-drop|newspaper|ne-resize|n-resize|move|middle|medium|ltr|lr-tb|lowercase|lower-roman|lower-alpha|loose|list-item|line|line-through|line-edge|lighter|left|keep-all|justify|italic|inter-word|inter-ideograph|inside|inset|inline|inline-block|inherit|inactive|ideograph-space|ideograph-parenthesis|ideograph-numeric|ideograph-alpha|horizontal|hidden|help|hand|groove|fixed|ellipsis|e-resize|double|dotted|distribute|distribute-space|distribute-letter|distribute-all-lines|disc|disabled|default|decimal|dashed|crosshair|collapse|col-resize|circle|char|center|capitalize|break-word|break-all|bottom|both|bolder|bold|block|bidi-override|below|baseline|auto|always|all-scroll|absolute|table|table-cell)\\b"},{begin:":",end:";",contains:[t,i,e.CSS_NUMBER_MODE,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,{className:"meta",begin:"!important"}]},{begin:"@(page|font-face)",lexemes:"@[a-z-]+",keywords:"@page @font-face"},{begin:"@",end:"[{;]",returnBegin:!0,keywords:"and or not only",contains:[{begin:"@[a-z-]+",className:"keyword"},t,e.QUOTE_STRING_MODE,e.APOS_STRING_MODE,i,e.CSS_NUMBER_MODE]}]}}}());hljs.registerLanguage("r",function(){"use strict";return function(e){var n="([a-zA-Z]|\\.[a-zA-Z.])[a-zA-Z0-9._]*";return{name:"R",contains:[e.HASH_COMMENT_MODE,{begin:n,keywords:{$pattern:n,keyword:"function if in break next repeat else for return switch while try tryCatch stop warning require library attach detach source setMethod setGeneric setGroupGeneric setClass ...",literal:"NULL NA TRUE FALSE T F Inf NaN NA_integer_|10 NA_real_|10 NA_character_|10 NA_complex_|10"},relevance:0},{className:"number",begin:"0[xX][0-9a-fA-F]+[Li]?\\b",relevance:0},{className:"number",begin:"\\d+(?:[eE][+\\-]?\\d*)?L\\b",relevance:0},{className:"number",begin:"\\d+\\.(?!\\d)(?:i\\b)?",relevance:0},{className:"number",begin:"\\d+(?:\\.\\d*)?(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{className:"number",begin:"\\.\\d+(?:[eE][+\\-]?\\d*)?i?\\b",relevance:0},{begin:"`",end:"`",relevance:0},{className:"string",contains:[e.BACKSLASH_ESCAPE],variants:[{begin:'"',end:'"'},{begin:"'",end:"'"}]}]}}}());hljs.registerLanguage("sql",function(){"use strict";return function(e){var t=e.COMMENT("--","$");return{name:"SQL",case_insensitive:!0,illegal:/[<>{}*]/,contains:[{beginKeywords:"begin end start commit rollback savepoint lock alter create drop rename call delete do handler insert load replace select truncate update set show pragma grant merge describe use explain help declare prepare execute deallocate release unlock purge reset change stop analyze cache flush optimize repair kill install uninstall checksum restore check backup revoke comment values with",end:/;/,endsWithParent:!0,keywords:{$pattern:/[\w\.]+/,keyword:"as abort abs absolute acc acce accep accept access accessed accessible account acos action activate add addtime admin administer advanced advise aes_decrypt aes_encrypt after agent aggregate ali alia alias all allocate allow alter always analyze ancillary and anti any anydata anydataset anyschema anytype apply archive archived archivelog are as asc ascii asin assembly assertion associate asynchronous at atan atn2 attr attri attrib attribu attribut attribute attributes audit authenticated authentication authid authors auto autoallocate autodblink autoextend automatic availability avg backup badfile basicfile before begin beginning benchmark between bfile bfile_base big bigfile bin binary_double binary_float binlog bit_and bit_count bit_length bit_or bit_xor bitmap blob_base block blocksize body both bound bucket buffer_cache buffer_pool build bulk by byte byteordermark bytes cache caching call calling cancel capacity cascade cascaded case cast catalog category ceil ceiling chain change changed char_base char_length character_length characters characterset charindex charset charsetform charsetid check checksum checksum_agg child choose chr chunk class cleanup clear client clob clob_base clone close cluster_id cluster_probability cluster_set clustering coalesce coercibility col collate collation collect colu colum column column_value columns columns_updated comment commit compact compatibility compiled complete composite_limit compound compress compute concat concat_ws concurrent confirm conn connec connect connect_by_iscycle connect_by_isleaf connect_by_root connect_time connection consider consistent constant constraint constraints constructor container content contents context contributors controlfile conv convert convert_tz corr corr_k corr_s corresponding corruption cos cost count count_big counted covar_pop covar_samp cpu_per_call cpu_per_session crc32 create creation critical cross cube cume_dist curdate current current_date current_time current_timestamp current_user cursor curtime customdatum cycle data database databases datafile datafiles datalength date_add date_cache date_format date_sub dateadd datediff datefromparts datename datepart datetime2fromparts day day_to_second dayname dayofmonth dayofweek dayofyear days db_role_change dbtimezone ddl deallocate declare decode decompose decrement decrypt deduplicate def defa defau defaul default defaults deferred defi defin define degrees delayed delegate delete delete_all delimited demand dense_rank depth dequeue des_decrypt des_encrypt des_key_file desc descr descri describ describe descriptor deterministic diagnostics difference dimension direct_load directory disable disable_all disallow disassociate discardfile disconnect diskgroup distinct distinctrow distribute distributed div do document domain dotnet double downgrade drop dumpfile duplicate duration each edition editionable editions element ellipsis else elsif elt empty enable enable_all enclosed encode encoding encrypt end end-exec endian enforced engine engines enqueue enterprise entityescaping eomonth error errors escaped evalname evaluate event eventdata events except exception exceptions exchange exclude excluding execu execut execute exempt exists exit exp expire explain explode export export_set extended extent external external_1 external_2 externally extract failed failed_login_attempts failover failure far fast feature_set feature_value fetch field fields file file_name_convert filesystem_like_logging final finish first first_value fixed flash_cache flashback floor flush following follows for forall force foreign form forma format found found_rows freelist freelists freepools fresh from from_base64 from_days ftp full function general generated get get_format get_lock getdate getutcdate global global_name globally go goto grant grants greatest group group_concat group_id grouping grouping_id groups gtid_subtract guarantee guard handler hash hashkeys having hea head headi headin heading heap help hex hierarchy high high_priority hosts hour hours http id ident_current ident_incr ident_seed identified identity idle_time if ifnull ignore iif ilike ilm immediate import in include including increment index indexes indexing indextype indicator indices inet6_aton inet6_ntoa inet_aton inet_ntoa infile initial initialized initially initrans inmemory inner innodb input insert install instance instantiable instr interface interleaved intersect into invalidate invisible is is_free_lock is_ipv4 is_ipv4_compat is_not is_not_null is_used_lock isdate isnull isolation iterate java join json json_exists keep keep_duplicates key keys kill language large last last_day last_insert_id last_value lateral lax lcase lead leading least leaves left len lenght length less level levels library like like2 like4 likec limit lines link list listagg little ln load load_file lob lobs local localtime localtimestamp locate locator lock locked log log10 log2 logfile logfiles logging logical logical_reads_per_call logoff logon logs long loop low low_priority lower lpad lrtrim ltrim main make_set makedate maketime managed management manual map mapping mask master master_pos_wait match matched materialized max maxextents maximize maxinstances maxlen maxlogfiles maxloghistory maxlogmembers maxsize maxtrans md5 measures median medium member memcompress memory merge microsecond mid migration min minextents minimum mining minus minute minutes minvalue missing mod mode model modification modify module monitoring month months mount move movement multiset mutex name name_const names nan national native natural nav nchar nclob nested never new newline next nextval no no_write_to_binlog noarchivelog noaudit nobadfile nocheck nocompress nocopy nocycle nodelay nodiscardfile noentityescaping noguarantee nokeep nologfile nomapping nomaxvalue nominimize nominvalue nomonitoring none noneditionable nonschema noorder nopr nopro noprom nopromp noprompt norely noresetlogs noreverse normal norowdependencies noschemacheck noswitch not nothing notice notnull notrim novalidate now nowait nth_value nullif nulls num numb numbe nvarchar nvarchar2 object ocicoll ocidate ocidatetime ociduration ociinterval ociloblocator ocinumber ociref ocirefcursor ocirowid ocistring ocitype oct octet_length of off offline offset oid oidindex old on online only opaque open operations operator optimal optimize option optionally or oracle oracle_date oradata ord ordaudio orddicom orddoc order ordimage ordinality ordvideo organization orlany orlvary out outer outfile outline output over overflow overriding package pad parallel parallel_enable parameters parent parse partial partition partitions pascal passing password password_grace_time password_lock_time password_reuse_max password_reuse_time password_verify_function patch path patindex pctincrease pctthreshold pctused pctversion percent percent_rank percentile_cont percentile_disc performance period period_add period_diff permanent physical pi pipe pipelined pivot pluggable plugin policy position post_transaction pow power pragma prebuilt precedes preceding precision prediction prediction_cost prediction_details prediction_probability prediction_set prepare present preserve prior priority private private_sga privileges procedural procedure procedure_analyze processlist profiles project prompt protection public publishingservername purge quarter query quick quiesce quota quotename radians raise rand range rank raw read reads readsize rebuild record records recover recovery recursive recycle redo reduced ref reference referenced references referencing refresh regexp_like register regr_avgx regr_avgy regr_count regr_intercept regr_r2 regr_slope regr_sxx regr_sxy reject rekey relational relative relaylog release release_lock relies_on relocate rely rem remainder rename repair repeat replace replicate replication required reset resetlogs resize resource respect restore restricted result result_cache resumable resume retention return returning returns reuse reverse revoke right rlike role roles rollback rolling rollup round row row_count rowdependencies rowid rownum rows rtrim rules safe salt sample save savepoint sb1 sb2 sb4 scan schema schemacheck scn scope scroll sdo_georaster sdo_topo_geometry search sec_to_time second seconds section securefile security seed segment select self semi sequence sequential serializable server servererror session session_user sessions_per_user set sets settings sha sha1 sha2 share shared shared_pool short show shrink shutdown si_averagecolor si_colorhistogram si_featurelist si_positionalcolor si_stillimage si_texture siblings sid sign sin size size_t sizes skip slave sleep smalldatetimefromparts smallfile snapshot some soname sort soundex source space sparse spfile split sql sql_big_result sql_buffer_result sql_cache sql_calc_found_rows sql_small_result sql_variant_property sqlcode sqldata sqlerror sqlname sqlstate sqrt square standalone standby start starting startup statement static statistics stats_binomial_test stats_crosstab stats_ks_test stats_mode stats_mw_test stats_one_way_anova stats_t_test_ stats_t_test_indep stats_t_test_one stats_t_test_paired stats_wsr_test status std stddev stddev_pop stddev_samp stdev stop storage store stored str str_to_date straight_join strcmp strict string struct stuff style subdate subpartition subpartitions substitutable substr substring subtime subtring_index subtype success sum suspend switch switchoffset switchover sync synchronous synonym sys sys_xmlagg sysasm sysaux sysdate sysdatetimeoffset sysdba sysoper system system_user sysutcdatetime table tables tablespace tablesample tan tdo template temporary terminated tertiary_weights test than then thread through tier ties time time_format time_zone timediff timefromparts timeout timestamp timestampadd timestampdiff timezone_abbr timezone_minute timezone_region to to_base64 to_date to_days to_seconds todatetimeoffset trace tracking transaction transactional translate translation treat trigger trigger_nestlevel triggers trim truncate try_cast try_convert try_parse type ub1 ub2 ub4 ucase unarchived unbounded uncompress under undo unhex unicode uniform uninstall union unique unix_timestamp unknown unlimited unlock unnest unpivot unrecoverable unsafe unsigned until untrusted unusable unused update updated upgrade upped upper upsert url urowid usable usage use use_stored_outlines user user_data user_resources users using utc_date utc_timestamp uuid uuid_short validate validate_password_strength validation valist value values var var_samp varcharc vari varia variab variabl variable variables variance varp varraw varrawc varray verify version versions view virtual visible void wait wallet warning warnings week weekday weekofyear wellformed when whene whenev wheneve whenever where while whitespace window with within without work wrapped xdb xml xmlagg xmlattributes xmlcast xmlcolattval xmlelement xmlexists xmlforest xmlindex xmlnamespaces xmlpi xmlquery xmlroot xmlschema xmlserialize xmltable xmltype xor year year_to_month years yearweek",literal:"true false null unknown",built_in:"array bigint binary bit blob bool boolean char character date dec decimal float int int8 integer interval number numeric real record serial serial8 smallint text time timestamp tinyint varchar varchar2 varying void"},contains:[{className:"string",begin:"'",end:"'",contains:[{begin:"''"}]},{className:"string",begin:'"',end:'"',contains:[{begin:'""'}]},{className:"string",begin:"`",end:"`"},e.C_NUMBER_MODE,e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]},e.C_BLOCK_COMMENT_MODE,t,e.HASH_COMMENT_MODE]}}}());hljs.registerLanguage("c",function(){"use strict";return function(e){var n=e.getLanguage("c-like").rawDefinition();return n.name="C",n.aliases=["c","h"],n}}());hljs.registerLanguage("json",function(){"use strict";return function(n){var e={literal:"true false null"},i=[n.C_LINE_COMMENT_MODE,n.C_BLOCK_COMMENT_MODE],t=[n.QUOTE_STRING_MODE,n.C_NUMBER_MODE],a={end:",",endsWithParent:!0,excludeEnd:!0,contains:t,keywords:e},l={begin:"{",end:"}",contains:[{className:"attr",begin:/"/,end:/"/,contains:[n.BACKSLASH_ESCAPE],illegal:"\\n"},n.inherit(a,{begin:/:/})].concat(i),illegal:"\\S"},s={begin:"\\[",end:"\\]",contains:[n.inherit(a)],illegal:"\\S"};return t.push(l,s),i.forEach((function(n){t.push(n)})),{name:"JSON",contains:t,keywords:e,illegal:"\\S"}}}());hljs.registerLanguage("python-repl",function(){"use strict";return function(n){return{aliases:["pycon"],contains:[{className:"meta",starts:{end:/ |$/,starts:{end:"$",subLanguage:"python"}},variants:[{begin:/^>>>(?=[ ]|$)/},{begin:/^\.\.\.(?=[ ]|$)/}]}]}}}());hljs.registerLanguage("markdown",function(){"use strict";return function(n){const e={begin:"<",end:">",subLanguage:"xml",relevance:0},a={begin:"\\[.+?\\][\\(\\[].*?[\\)\\]]",returnBegin:!0,contains:[{className:"string",begin:"\\[",end:"\\]",excludeBegin:!0,returnEnd:!0,relevance:0},{className:"link",begin:"\\]\\(",end:"\\)",excludeBegin:!0,excludeEnd:!0},{className:"symbol",begin:"\\]\\[",end:"\\]",excludeBegin:!0,excludeEnd:!0}],relevance:10},i={className:"strong",contains:[],variants:[{begin:/_{2}/,end:/_{2}/},{begin:/\*{2}/,end:/\*{2}/}]},s={className:"emphasis",contains:[],variants:[{begin:/\*(?!\*)/,end:/\*/},{begin:/_(?!_)/,end:/_/,relevance:0}]};i.contains.push(s),s.contains.push(i);var c=[e,a];return i.contains=i.contains.concat(c),s.contains=s.contains.concat(c),{name:"Markdown",aliases:["md","mkdown","mkd"],contains:[{className:"section",variants:[{begin:"^#{1,6}",end:"$",contains:c=c.concat(i,s)},{begin:"(?=^.+?\\n[=-]{2,}$)",contains:[{begin:"^[=-]*$"},{begin:"^",end:"\\n",contains:c}]}]},e,{className:"bullet",begin:"^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)",end:"\\s+",excludeEnd:!0},i,s,{className:"quote",begin:"^>\\s+",contains:c,end:"$"},{className:"code",variants:[{begin:"(`{3,})(.|\\n)*?\\1`*[ ]*"},{begin:"(~{3,})(.|\\n)*?\\1~*[ ]*"},{begin:"```",end:"```+[ ]*$"},{begin:"~~~",end:"~~~+[ ]*$"},{begin:"`.+?`"},{begin:"(?=^( {4}|\\t))",contains:[{begin:"^( {4}|\\t)",end:"(\\n)$"}],relevance:0}]},{begin:"^[-\\*]{3,}",end:"$"},a,{begin:/^\[[^\n]+\]:/,returnBegin:!0,contains:[{className:"symbol",begin:/\[/,end:/\]/,excludeBegin:!0,excludeEnd:!0},{className:"link",begin:/:\s*/,end:/$/,excludeBegin:!0}]}]}}}());hljs.registerLanguage("javascript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);function s(e){return r("(?=",e,")")}function r(...e){return e.map(e=>(function(e){return e?"string"==typeof e?e:e.source:null})(e)).join("")}return function(t){var i="[A-Za-z$_][0-9A-Za-z$_]*",c={begin:/<[A-Za-z0-9\\._:-]+/,end:/\/[A-Za-z0-9\\._:-]+>|\/>/},o={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.join(" "),literal:n.join(" "),built_in:a.join(" ")},l={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:t.C_NUMBER_RE+"n?"}],relevance:0},E={className:"subst",begin:"\\$\\{",end:"\\}",keywords:o,contains:[]},d={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"xml"}},g={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[t.BACKSLASH_ESCAPE,E],subLanguage:"css"}},u={className:"string",begin:"`",end:"`",contains:[t.BACKSLASH_ESCAPE,E]};E.contains=[t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,l,t.REGEXP_MODE];var b=E.contains.concat([{begin:/\(/,end:/\)/,contains:["self"].concat(E.contains,[t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE])},t.C_BLOCK_COMMENT_MODE,t.C_LINE_COMMENT_MODE]),_={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,contains:b};return{name:"JavaScript",aliases:["js","jsx","mjs","cjs"],keywords:o,contains:[t.SHEBANG({binary:"node",relevance:5}),{className:"meta",relevance:10,begin:/^\s*['"]use (strict|asm)['"]/},t.APOS_STRING_MODE,t.QUOTE_STRING_MODE,d,g,u,t.C_LINE_COMMENT_MODE,t.COMMENT("/\\*\\*","\\*/",{relevance:0,contains:[{className:"doctag",begin:"@[A-Za-z]+",contains:[{className:"type",begin:"\\{",end:"\\}",relevance:0},{className:"variable",begin:i+"(?=\\s*(-)|$)",endsParent:!0,relevance:0},{begin:/(?=[^\n])\s/,relevance:0}]}]}),t.C_BLOCK_COMMENT_MODE,l,{begin:r(/[{,\n]\s*/,s(r(/(((\/\/.*)|(\/\*(.|\n)*\*\/))\s*)*/,i+"\\s*:"))),relevance:0,contains:[{className:"attr",begin:i+s("\\s*:"),relevance:0}]},{begin:"("+t.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[t.C_LINE_COMMENT_MODE,t.C_BLOCK_COMMENT_MODE,t.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+t.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:t.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:o,contains:b}]}]},{begin:/,/,relevance:0},{className:"",begin:/\s/,end:/\s*/,skip:!0},{variants:[{begin:"<>",end:""},{begin:c.begin,end:c.end}],subLanguage:"xml",contains:[{begin:c.begin,end:c.end,skip:!0,contains:["self"]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/\{/,excludeEnd:!0,contains:[t.inherit(t.TITLE_MODE,{begin:i}),_],illegal:/\[|%/},{begin:/\$[(.]/},t.METHOD_GUARD,{className:"class",beginKeywords:"class",end:/[{;=]/,excludeEnd:!0,illegal:/[:"\[\]]/,contains:[{beginKeywords:"extends"},t.UNDERSCORE_TITLE_MODE]},{beginKeywords:"constructor",end:/\{/,excludeEnd:!0},{begin:"(get|set)\\s+(?="+i+"\\()",end:/{/,keywords:"get set",contains:[t.inherit(t.TITLE_MODE,{begin:i}),{begin:/\(\)/},_]}],illegal:/#(?!!)/}}}());hljs.registerLanguage("typescript",function(){"use strict";const e=["as","in","of","if","for","while","finally","var","new","function","do","return","void","else","break","catch","instanceof","with","throw","case","default","try","switch","continue","typeof","delete","let","yield","const","class","debugger","async","await","static","import","from","export","extends"],n=["true","false","null","undefined","NaN","Infinity"],a=[].concat(["setInterval","setTimeout","clearInterval","clearTimeout","require","exports","eval","isFinite","isNaN","parseFloat","parseInt","decodeURI","decodeURIComponent","encodeURI","encodeURIComponent","escape","unescape"],["arguments","this","super","console","window","document","localStorage","module","global"],["Intl","DataView","Number","Math","Date","String","RegExp","Object","Function","Boolean","Error","Symbol","Set","Map","WeakSet","WeakMap","Proxy","Reflect","JSON","Promise","Float64Array","Int16Array","Int32Array","Int8Array","Uint16Array","Uint32Array","Float32Array","Array","Uint8Array","Uint8ClampedArray","ArrayBuffer"],["EvalError","InternalError","RangeError","ReferenceError","SyntaxError","TypeError","URIError"]);return function(r){var t={$pattern:"[A-Za-z$_][0-9A-Za-z$_]*",keyword:e.concat(["type","namespace","typedef","interface","public","private","protected","implements","declare","abstract","readonly"]).join(" "),literal:n.join(" "),built_in:a.concat(["any","void","number","boolean","string","object","never","enum"]).join(" ")},s={className:"meta",begin:"@[A-Za-z$_][0-9A-Za-z$_]*"},i={className:"number",variants:[{begin:"\\b(0[bB][01]+)n?"},{begin:"\\b(0[oO][0-7]+)n?"},{begin:r.C_NUMBER_RE+"n?"}],relevance:0},o={className:"subst",begin:"\\$\\{",end:"\\}",keywords:t,contains:[]},c={begin:"html`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"xml"}},l={begin:"css`",end:"",starts:{end:"`",returnEnd:!1,contains:[r.BACKSLASH_ESCAPE,o],subLanguage:"css"}},E={className:"string",begin:"`",end:"`",contains:[r.BACKSLASH_ESCAPE,o]};o.contains=[r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,i,r.REGEXP_MODE];var d={begin:"\\(",end:/\)/,keywords:t,contains:["self",r.QUOTE_STRING_MODE,r.APOS_STRING_MODE,r.NUMBER_MODE]},u={className:"params",begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,s,d]};return{name:"TypeScript",aliases:["ts"],keywords:t,contains:[r.SHEBANG(),{className:"meta",begin:/^\s*['"]use strict['"]/},r.APOS_STRING_MODE,r.QUOTE_STRING_MODE,c,l,E,r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,i,{begin:"("+r.RE_STARTERS_RE+"|\\b(case|return|throw)\\b)\\s*",keywords:"return throw case",contains:[r.C_LINE_COMMENT_MODE,r.C_BLOCK_COMMENT_MODE,r.REGEXP_MODE,{className:"function",begin:"(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|"+r.UNDERSCORE_IDENT_RE+")\\s*=>",returnBegin:!0,end:"\\s*=>",contains:[{className:"params",variants:[{begin:r.UNDERSCORE_IDENT_RE},{className:null,begin:/\(\s*\)/,skip:!0},{begin:/\(/,end:/\)/,excludeBegin:!0,excludeEnd:!0,keywords:t,contains:d.contains}]}]}],relevance:0},{className:"function",beginKeywords:"function",end:/[\{;]/,excludeEnd:!0,keywords:t,contains:["self",r.inherit(r.TITLE_MODE,{begin:"[A-Za-z$_][0-9A-Za-z$_]*"}),u],illegal:/%/,relevance:0},{beginKeywords:"constructor",end:/[\{;]/,excludeEnd:!0,contains:["self",u]},{begin:/module\./,keywords:{built_in:"module"},relevance:0},{beginKeywords:"module",end:/\{/,excludeEnd:!0},{beginKeywords:"interface",end:/\{/,excludeEnd:!0,keywords:"interface extends"},{begin:/\$[(.]/},{begin:"\\."+r.IDENT_RE,relevance:0},s,d]}}}());hljs.registerLanguage("plaintext",function(){"use strict";return function(t){return{name:"Plain text",aliases:["text","txt"],disableAutodetect:!0}}}());hljs.registerLanguage("less",function(){"use strict";return function(e){var n="([\\w-]+|@{[\\w-]+})",a=[],s=[],t=function(e){return{className:"string",begin:"~?"+e+".*?"+e}},r=function(e,n,a){return{className:e,begin:n,relevance:a}},i={begin:"\\(",end:"\\)",contains:s,relevance:0};s.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,t("'"),t('"'),e.CSS_NUMBER_MODE,{begin:"(url|data-uri)\\(",starts:{className:"string",end:"[\\)\\n]",excludeEnd:!0}},r("number","#[0-9A-Fa-f]+\\b"),i,r("variable","@@?[\\w-]+",10),r("variable","@{[\\w-]+}"),r("built_in","~?`[^`]*?`"),{className:"attribute",begin:"[\\w-]+\\s*:",end:":",returnBegin:!0,excludeEnd:!0},{className:"meta",begin:"!important"});var c=s.concat({begin:"{",end:"}",contains:a}),l={beginKeywords:"when",endsWithParent:!0,contains:[{beginKeywords:"and not"}].concat(s)},o={begin:n+"\\s*:",returnBegin:!0,end:"[;}]",relevance:0,contains:[{className:"attribute",begin:n,end:":",excludeEnd:!0,starts:{endsWithParent:!0,illegal:"[<=$]",relevance:0,contains:s}}]},g={className:"keyword",begin:"@(import|media|charset|font-face|(-[a-z]+-)?keyframes|supports|document|namespace|page|viewport|host)\\b",starts:{end:"[;{}]",returnEnd:!0,contains:s,relevance:0}},d={className:"variable",variants:[{begin:"@[\\w-]+\\s*:",relevance:15},{begin:"@[\\w-]+"}],starts:{end:"[;}]",returnEnd:!0,contains:c}},b={variants:[{begin:"[\\.#:&\\[>]",end:"[;{}]"},{begin:n,end:"{"}],returnBegin:!0,returnEnd:!0,illegal:"[<='$\"]",relevance:0,contains:[e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,l,r("keyword","all\\b"),r("variable","@{[\\w-]+}"),r("selector-tag",n+"%?",0),r("selector-id","#"+n),r("selector-class","\\."+n,0),r("selector-tag","&",0),{className:"selector-attr",begin:"\\[",end:"\\]"},{className:"selector-pseudo",begin:/:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/},{begin:"\\(",end:"\\)",contains:c},{begin:"!important"}]};return a.push(e.C_LINE_COMMENT_MODE,e.C_BLOCK_COMMENT_MODE,g,d,o,b),{name:"Less",case_insensitive:!0,illegal:"[=>'/<($\"]",contains:a}}}());hljs.registerLanguage("lua",function(){"use strict";return function(e){var t={begin:"\\[=*\\[",end:"\\]=*\\]",contains:["self"]},a=[e.COMMENT("--(?!\\[=*\\[)","$"),e.COMMENT("--\\[=*\\[","\\]=*\\]",{contains:[t],relevance:10})];return{name:"Lua",keywords:{$pattern:e.UNDERSCORE_IDENT_RE,literal:"true false nil",keyword:"and break do else elseif end for goto if in local not or repeat return then until while",built_in:"_G _ENV _VERSION __index __newindex __mode __call __metatable __tostring __len __gc __add __sub __mul __div __mod __pow __concat __unm __eq __lt __le assert collectgarbage dofile error getfenv getmetatable ipairs load loadfile loadstring module next pairs pcall print rawequal rawget rawset require select setfenv setmetatable tonumber tostring type unpack xpcall arg self coroutine resume yield status wrap create running debug getupvalue debug sethook getmetatable gethook setmetatable setlocal traceback setfenv getinfo setupvalue getlocal getregistry getfenv io lines write close flush open output type read stderr stdin input stdout popen tmpfile math log max acos huge ldexp pi cos tanh pow deg tan cosh sinh random randomseed frexp ceil floor rad abs sqrt modf asin min mod fmod log10 atan2 exp sin atan os exit setlocale date getenv difftime remove time clock tmpname rename execute package preload loadlib loaded loaders cpath config path seeall string sub upper len gfind rep find match char dump gmatch reverse byte format gsub lower table setn insert getn foreachi maxn foreach concat sort remove"},contains:a.concat([{className:"function",beginKeywords:"function",end:"\\)",contains:[e.inherit(e.TITLE_MODE,{begin:"([_a-zA-Z]\\w*\\.)*([_a-zA-Z]\\w*:)?[_a-zA-Z]\\w*"}),{className:"params",begin:"\\(",endsWithParent:!0,contains:a}].concat(a)},e.C_NUMBER_MODE,e.APOS_STRING_MODE,e.QUOTE_STRING_MODE,{className:"string",begin:"\\[=*\\[",end:"\\]=*\\]",contains:[t],relevance:5}])}}}()); +var hljs = (function () { + "use strict"; + function e(n) { + Object.freeze(n); + var t = "function" == typeof n; + return ( + Object.getOwnPropertyNames(n).forEach(function (r) { + !Object.hasOwnProperty.call(n, r) || + null === n[r] || + ("object" != typeof n[r] && "function" != typeof n[r]) || + (t && ("caller" === r || "callee" === r || "arguments" === r)) || + Object.isFrozen(n[r]) || + e(n[r]); + }), + n + ); + } + class n { + constructor(e) { + void 0 === e.data && (e.data = {}), (this.data = e.data); + } + ignoreMatch() { + this.ignore = !0; + } + } + function t(e) { + return e + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } + function r(e, ...n) { + var t = {}; + for (const n in e) t[n] = e[n]; + return ( + n.forEach(function (e) { + for (const n in e) t[n] = e[n]; + }), + t + ); + } + function a(e) { + return e.nodeName.toLowerCase(); + } + var i = Object.freeze({ + __proto__: null, + escapeHTML: t, + inherit: r, + nodeStream: function (e) { + var n = []; + return ( + (function e(t, r) { + for (var i = t.firstChild; i; i = i.nextSibling) + 3 === i.nodeType + ? (r += i.nodeValue.length) + : 1 === i.nodeType && + (n.push({ event: "start", offset: r, node: i }), + (r = e(i, r)), + a(i).match(/br|hr|img|input/) || + n.push({ event: "stop", offset: r, node: i })); + return r; + })(e, 0), + n + ); + }, + mergeStreams: function (e, n, r) { + var i = 0, + s = "", + o = []; + function l() { + return e.length && n.length + ? e[0].offset !== n[0].offset + ? e[0].offset < n[0].offset + ? e + : n + : "start" === n[0].event + ? e + : n + : e.length + ? e + : n; + } + function c(e) { + s += + "<" + + a(e) + + [].map + .call(e.attributes, function (e) { + return " " + e.nodeName + '="' + t(e.value) + '"'; + }) + .join("") + + ">"; + } + function u(e) { + s += ""; + } + function d(e) { + ("start" === e.event ? c : u)(e.node); + } + for (; e.length || n.length; ) { + var g = l(); + if ( + ((s += t(r.substring(i, g[0].offset))), (i = g[0].offset), g === e) + ) { + o.reverse().forEach(u); + do { + d(g.splice(0, 1)[0]), (g = l()); + } while (g === e && g.length && g[0].offset === i); + o.reverse().forEach(c); + } else + "start" === g[0].event ? o.push(g[0].node) : o.pop(), + d(g.splice(0, 1)[0]); + } + return s + t(r.substr(i)); + }, + }); + const s = "
", + o = (e) => !!e.kind; + class l { + constructor(e, n) { + (this.buffer = ""), (this.classPrefix = n.classPrefix), e.walk(this); + } + addText(e) { + this.buffer += t(e); + } + openNode(e) { + if (!o(e)) return; + let n = e.kind; + e.sublanguage || (n = `${this.classPrefix}${n}`), this.span(n); + } + closeNode(e) { + o(e) && (this.buffer += s); + } + value() { + return this.buffer; + } + span(e) { + this.buffer += ``; + } + } + class c { + constructor() { + (this.rootNode = { children: [] }), (this.stack = [this.rootNode]); + } + get top() { + return this.stack[this.stack.length - 1]; + } + get root() { + return this.rootNode; + } + add(e) { + this.top.children.push(e); + } + openNode(e) { + const n = { kind: e, children: [] }; + this.add(n), this.stack.push(n); + } + closeNode() { + if (this.stack.length > 1) return this.stack.pop(); + } + closeAllNodes() { + for (; this.closeNode(); ); + } + toJSON() { + return JSON.stringify(this.rootNode, null, 4); + } + walk(e) { + return this.constructor._walk(e, this.rootNode); + } + static _walk(e, n) { + return ( + "string" == typeof n + ? e.addText(n) + : n.children && + (e.openNode(n), + n.children.forEach((n) => this._walk(e, n)), + e.closeNode(n)), + e + ); + } + static _collapse(e) { + "string" != typeof e && + e.children && + (e.children.every((e) => "string" == typeof e) + ? (e.children = [e.children.join("")]) + : e.children.forEach((e) => { + c._collapse(e); + })); + } + } + class u extends c { + constructor(e) { + super(), (this.options = e); + } + addKeyword(e, n) { + "" !== e && (this.openNode(n), this.addText(e), this.closeNode()); + } + addText(e) { + "" !== e && this.add(e); + } + addSublanguage(e, n) { + const t = e.root; + (t.kind = n), (t.sublanguage = !0), this.add(t); + } + toHTML() { + return new l(this, this.options).value(); + } + finalize() { + return !0; + } + } + function d(e) { + return e ? ("string" == typeof e ? e : e.source) : null; + } + const g = + "(-?)(\\b0[xX][a-fA-F0-9]+|(\\b\\d+(\\.\\d*)?|\\.\\d+)([eE][-+]?\\d+)?)", + h = { begin: "\\\\[\\s\\S]", relevance: 0 }, + f = { + className: "string", + begin: "'", + end: "'", + illegal: "\\n", + contains: [h], + }, + p = { + className: "string", + begin: '"', + end: '"', + illegal: "\\n", + contains: [h], + }, + b = { + begin: + /\b(a|an|the|are|I'm|isn't|don't|doesn't|won't|but|just|should|pretty|simply|enough|gonna|going|wtf|so|such|will|you|your|they|like|more)\b/, + }, + m = function (e, n, t = {}) { + var a = r({ className: "comment", begin: e, end: n, contains: [] }, t); + return ( + a.contains.push(b), + a.contains.push({ + className: "doctag", + begin: "(?:TODO|FIXME|NOTE|BUG|OPTIMIZE|HACK|XXX):", + relevance: 0, + }), + a + ); + }, + v = m("//", "$"), + x = m("/\\*", "\\*/"), + E = m("#", "$"); + var _ = Object.freeze({ + __proto__: null, + IDENT_RE: "[a-zA-Z]\\w*", + UNDERSCORE_IDENT_RE: "[a-zA-Z_]\\w*", + NUMBER_RE: "\\b\\d+(\\.\\d+)?", + C_NUMBER_RE: g, + BINARY_NUMBER_RE: "\\b(0b[01]+)", + RE_STARTERS_RE: + "!|!=|!==|%|%=|&|&&|&=|\\*|\\*=|\\+|\\+=|,|-|-=|/=|/|:|;|<<|<<=|<=|<|===|==|=|>>>=|>>=|>=|>>>|>>|>|\\?|\\[|\\{|\\(|\\^|\\^=|\\||\\|=|\\|\\||~", + SHEBANG: (e = {}) => { + const n = /^#![ ]*\//; + return ( + e.binary && + (e.begin = (function (...e) { + return e.map((e) => d(e)).join(""); + })(n, /.*\b/, e.binary, /\b.*/)), + r( + { + className: "meta", + begin: n, + end: /$/, + relevance: 0, + "on:begin": (e, n) => { + 0 !== e.index && n.ignoreMatch(); + }, + }, + e + ) + ); + }, + BACKSLASH_ESCAPE: h, + APOS_STRING_MODE: f, + QUOTE_STRING_MODE: p, + PHRASAL_WORDS_MODE: b, + COMMENT: m, + C_LINE_COMMENT_MODE: v, + C_BLOCK_COMMENT_MODE: x, + HASH_COMMENT_MODE: E, + NUMBER_MODE: { + className: "number", + begin: "\\b\\d+(\\.\\d+)?", + relevance: 0, + }, + C_NUMBER_MODE: { className: "number", begin: g, relevance: 0 }, + BINARY_NUMBER_MODE: { + className: "number", + begin: "\\b(0b[01]+)", + relevance: 0, + }, + CSS_NUMBER_MODE: { + className: "number", + begin: + "\\b\\d+(\\.\\d+)?(%|em|ex|ch|rem|vw|vh|vmin|vmax|cm|mm|in|pt|pc|px|deg|grad|rad|turn|s|ms|Hz|kHz|dpi|dpcm|dppx)?", + relevance: 0, + }, + REGEXP_MODE: { + begin: /(?=\/[^/\n]*\/)/, + contains: [ + { + className: "regexp", + begin: /\//, + end: /\/[gimuy]*/, + illegal: /\n/, + contains: [ + h, + { begin: /\[/, end: /\]/, relevance: 0, contains: [h] }, + ], + }, + ], + }, + TITLE_MODE: { className: "title", begin: "[a-zA-Z]\\w*", relevance: 0 }, + UNDERSCORE_TITLE_MODE: { + className: "title", + begin: "[a-zA-Z_]\\w*", + relevance: 0, + }, + METHOD_GUARD: { begin: "\\.\\s*[a-zA-Z_]\\w*", relevance: 0 }, + END_SAME_AS_BEGIN: function (e) { + return Object.assign(e, { + "on:begin": (e, n) => { + n.data._beginMatch = e[1]; + }, + "on:end": (e, n) => { + n.data._beginMatch !== e[1] && n.ignoreMatch(); + }, + }); + }, + }), + N = "of and for in not or if then".split(" "); + function w(e, n) { + return n + ? +n + : (function (e) { + return N.includes(e.toLowerCase()); + })(e) + ? 0 + : 1; + } + const R = t, + y = r, + { nodeStream: k, mergeStreams: O } = i, + M = Symbol("nomatch"); + return (function (t) { + var a = [], + i = {}, + s = {}, + o = [], + l = !0, + c = /(^(<[^>]+>|\t|)+|\n)/gm, + g = + "Could not find the language '{}', did you forget to load/include a language module?"; + const h = { disableAutodetect: !0, name: "Plain text", contains: [] }; + var f = { + noHighlightRe: /^(no-?highlight)$/i, + languageDetectRe: /\blang(?:uage)?-([\w-]+)\b/i, + classPrefix: "hljs-", + tabReplace: null, + useBR: !1, + languages: null, + __emitter: u, + }; + function p(e) { + return f.noHighlightRe.test(e); + } + function b(e, n, t, r) { + var a = { code: n, language: e }; + S("before:highlight", a); + var i = a.result ? a.result : m(a.language, a.code, t, r); + return (i.code = a.code), S("after:highlight", i), i; + } + function m(e, t, a, s) { + var o = t; + function c(e, n) { + var t = E.case_insensitive ? n[0].toLowerCase() : n[0]; + return ( + Object.prototype.hasOwnProperty.call(e.keywords, t) && e.keywords[t] + ); + } + function u() { + null != y.subLanguage + ? (function () { + if ("" !== A) { + var e = null; + if ("string" == typeof y.subLanguage) { + if (!i[y.subLanguage]) return void O.addText(A); + (e = m(y.subLanguage, A, !0, k[y.subLanguage])), + (k[y.subLanguage] = e.top); + } else e = v(A, y.subLanguage.length ? y.subLanguage : null); + y.relevance > 0 && (I += e.relevance), + O.addSublanguage(e.emitter, e.language); + } + })() + : (function () { + if (!y.keywords) return void O.addText(A); + let e = 0; + y.keywordPatternRe.lastIndex = 0; + let n = y.keywordPatternRe.exec(A), + t = ""; + for (; n; ) { + t += A.substring(e, n.index); + const r = c(y, n); + if (r) { + const [e, a] = r; + O.addText(t), (t = ""), (I += a), O.addKeyword(n[0], e); + } else t += n[0]; + (e = y.keywordPatternRe.lastIndex), + (n = y.keywordPatternRe.exec(A)); + } + (t += A.substr(e)), O.addText(t); + })(), + (A = ""); + } + function h(e) { + return ( + e.className && O.openNode(e.className), + (y = Object.create(e, { parent: { value: y } })) + ); + } + function p(e) { + return 0 === y.matcher.regexIndex ? ((A += e[0]), 1) : ((L = !0), 0); + } + var b = {}; + function x(t, r) { + var i = r && r[0]; + if (((A += t), null == i)) return u(), 0; + if ( + "begin" === b.type && + "end" === r.type && + b.index === r.index && + "" === i + ) { + if (((A += o.slice(r.index, r.index + 1)), !l)) { + const n = Error("0 width match regex"); + throw ((n.languageName = e), (n.badRule = b.rule), n); + } + return 1; + } + if (((b = r), "begin" === r.type)) + return (function (e) { + var t = e[0], + r = e.rule; + const a = new n(r), + i = [r.__beforeBegin, r["on:begin"]]; + for (const n of i) if (n && (n(e, a), a.ignore)) return p(t); + return ( + r && + r.endSameAsBegin && + (r.endRe = RegExp( + t.replace(/[-/\\^$*+?.()|[\]{}]/g, "\\$&"), + "m" + )), + r.skip + ? (A += t) + : (r.excludeBegin && (A += t), + u(), + r.returnBegin || r.excludeBegin || (A = t)), + h(r), + r.returnBegin ? 0 : t.length + ); + })(r); + if ("illegal" === r.type && !a) { + const e = Error( + 'Illegal lexeme "' + + i + + '" for mode "' + + (y.className || "") + + '"' + ); + throw ((e.mode = y), e); + } + if ("end" === r.type) { + var s = (function (e) { + var t = e[0], + r = o.substr(e.index), + a = (function e(t, r, a) { + let i = (function (e, n) { + var t = e && e.exec(n); + return t && 0 === t.index; + })(t.endRe, a); + if (i) { + if (t["on:end"]) { + const e = new n(t); + t["on:end"](r, e), e.ignore && (i = !1); + } + if (i) { + for (; t.endsParent && t.parent; ) t = t.parent; + return t; + } + } + if (t.endsWithParent) return e(t.parent, r, a); + })(y, e, r); + if (!a) return M; + var i = y; + i.skip + ? (A += t) + : (i.returnEnd || i.excludeEnd || (A += t), + u(), + i.excludeEnd && (A = t)); + do { + y.className && O.closeNode(), + y.skip || y.subLanguage || (I += y.relevance), + (y = y.parent); + } while (y !== a.parent); + return ( + a.starts && + (a.endSameAsBegin && (a.starts.endRe = a.endRe), h(a.starts)), + i.returnEnd ? 0 : t.length + ); + })(r); + if (s !== M) return s; + } + if ("illegal" === r.type && "" === i) return 1; + if (B > 1e5 && B > 3 * r.index) + throw Error( + "potential infinite loop, way more iterations than matches" + ); + return (A += i), i.length; + } + var E = T(e); + if (!E) + throw ( + (console.error(g.replace("{}", e)), + Error('Unknown language: "' + e + '"')) + ); + var _ = (function (e) { + function n(n, t) { + return RegExp( + d(n), + "m" + (e.case_insensitive ? "i" : "") + (t ? "g" : "") + ); + } + class t { + constructor() { + (this.matchIndexes = {}), + (this.regexes = []), + (this.matchAt = 1), + (this.position = 0); + } + addRule(e, n) { + (n.position = this.position++), + (this.matchIndexes[this.matchAt] = n), + this.regexes.push([n, e]), + (this.matchAt += + (function (e) { + return RegExp(e.toString() + "|").exec("").length - 1; + })(e) + 1); + } + compile() { + 0 === this.regexes.length && (this.exec = () => null); + const e = this.regexes.map((e) => e[1]); + (this.matcherRe = n( + (function (e, n = "|") { + for ( + var t = /\[(?:[^\\\]]|\\.)*\]|\(\??|\\([1-9][0-9]*)|\\./, + r = 0, + a = "", + i = 0; + i < e.length; + i++ + ) { + var s = (r += 1), + o = d(e[i]); + for (i > 0 && (a += n), a += "("; o.length > 0; ) { + var l = t.exec(o); + if (null == l) { + a += o; + break; + } + (a += o.substring(0, l.index)), + (o = o.substring(l.index + l[0].length)), + "\\" === l[0][0] && l[1] + ? (a += "\\" + (+l[1] + s)) + : ((a += l[0]), "(" === l[0] && r++); + } + a += ")"; + } + return a; + })(e), + !0 + )), + (this.lastIndex = 0); + } + exec(e) { + this.matcherRe.lastIndex = this.lastIndex; + const n = this.matcherRe.exec(e); + if (!n) return null; + const t = n.findIndex((e, n) => n > 0 && void 0 !== e), + r = this.matchIndexes[t]; + return n.splice(0, t), Object.assign(n, r); + } + } + class a { + constructor() { + (this.rules = []), + (this.multiRegexes = []), + (this.count = 0), + (this.lastIndex = 0), + (this.regexIndex = 0); + } + getMatcher(e) { + if (this.multiRegexes[e]) return this.multiRegexes[e]; + const n = new t(); + return ( + this.rules.slice(e).forEach(([e, t]) => n.addRule(e, t)), + n.compile(), + (this.multiRegexes[e] = n), + n + ); + } + considerAll() { + this.regexIndex = 0; + } + addRule(e, n) { + this.rules.push([e, n]), "begin" === n.type && this.count++; + } + exec(e) { + const n = this.getMatcher(this.regexIndex); + n.lastIndex = this.lastIndex; + const t = n.exec(e); + return ( + t && + ((this.regexIndex += t.position + 1), + this.regexIndex === this.count && (this.regexIndex = 0)), + t + ); + } + } + function i(e, n) { + const t = e.input[e.index - 1], + r = e.input[e.index + e[0].length]; + ("." !== t && "." !== r) || n.ignoreMatch(); + } + if (e.contains && e.contains.includes("self")) + throw Error( + "ERR: contains `self` is not supported at the top-level of a language. See documentation." + ); + return (function t(s, o) { + const l = s; + if (s.compiled) return l; + (s.compiled = !0), + (s.__beforeBegin = null), + (s.keywords = s.keywords || s.beginKeywords); + let c = null; + if ( + ("object" == typeof s.keywords && + ((c = s.keywords.$pattern), delete s.keywords.$pattern), + s.keywords && + (s.keywords = (function (e, n) { + var t = {}; + return ( + "string" == typeof e + ? r("keyword", e) + : Object.keys(e).forEach(function (n) { + r(n, e[n]); + }), + t + ); + function r(e, r) { + n && (r = r.toLowerCase()), + r.split(" ").forEach(function (n) { + var r = n.split("|"); + t[r[0]] = [e, w(r[0], r[1])]; + }); + } + })(s.keywords, e.case_insensitive)), + s.lexemes && c) + ) + throw Error( + "ERR: Prefer `keywords.$pattern` to `mode.lexemes`, BOTH are not allowed. (see mode reference) " + ); + return ( + (l.keywordPatternRe = n(s.lexemes || c || /\w+/, !0)), + o && + (s.beginKeywords && + ((s.begin = + "\\b(" + + s.beginKeywords.split(" ").join("|") + + ")(?=\\b|\\s)"), + (s.__beforeBegin = i)), + s.begin || (s.begin = /\B|\b/), + (l.beginRe = n(s.begin)), + s.endSameAsBegin && (s.end = s.begin), + s.end || s.endsWithParent || (s.end = /\B|\b/), + s.end && (l.endRe = n(s.end)), + (l.terminator_end = d(s.end) || ""), + s.endsWithParent && + o.terminator_end && + (l.terminator_end += (s.end ? "|" : "") + o.terminator_end)), + s.illegal && (l.illegalRe = n(s.illegal)), + void 0 === s.relevance && (s.relevance = 1), + s.contains || (s.contains = []), + (s.contains = [].concat( + ...s.contains.map(function (e) { + return (function (e) { + return ( + e.variants && + !e.cached_variants && + (e.cached_variants = e.variants.map(function (n) { + return r(e, { variants: null }, n); + })), + e.cached_variants + ? e.cached_variants + : (function e(n) { + return !!n && (n.endsWithParent || e(n.starts)); + })(e) + ? r(e, { starts: e.starts ? r(e.starts) : null }) + : Object.isFrozen(e) + ? r(e) + : e + ); + })("self" === e ? s : e); + }) + )), + s.contains.forEach(function (e) { + t(e, l); + }), + s.starts && t(s.starts, o), + (l.matcher = (function (e) { + const n = new a(); + return ( + e.contains.forEach((e) => + n.addRule(e.begin, { rule: e, type: "begin" }) + ), + e.terminator_end && + n.addRule(e.terminator_end, { type: "end" }), + e.illegal && n.addRule(e.illegal, { type: "illegal" }), + n + ); + })(l)), + l + ); + })(e); + })(E), + N = "", + y = s || _, + k = {}, + O = new f.__emitter(f); + !(function () { + for (var e = [], n = y; n !== E; n = n.parent) + n.className && e.unshift(n.className); + e.forEach((e) => O.openNode(e)); + })(); + var A = "", + I = 0, + S = 0, + B = 0, + L = !1; + try { + for (y.matcher.considerAll(); ; ) { + B++, + L ? (L = !1) : ((y.matcher.lastIndex = S), y.matcher.considerAll()); + const e = y.matcher.exec(o); + if (!e) break; + const n = x(o.substring(S, e.index), e); + S = e.index + n; + } + return ( + x(o.substr(S)), + O.closeAllNodes(), + O.finalize(), + (N = O.toHTML()), + { + relevance: I, + value: N, + language: e, + illegal: !1, + emitter: O, + top: y, + } + ); + } catch (n) { + if (n.message && n.message.includes("Illegal")) + return { + illegal: !0, + illegalBy: { + msg: n.message, + context: o.slice(S - 100, S + 100), + mode: n.mode, + }, + sofar: N, + relevance: 0, + value: R(o), + emitter: O, + }; + if (l) + return { + illegal: !1, + relevance: 0, + value: R(o), + emitter: O, + language: e, + top: y, + errorRaised: n, + }; + throw n; + } + } + function v(e, n) { + n = n || f.languages || Object.keys(i); + var t = (function (e) { + const n = { + relevance: 0, + emitter: new f.__emitter(f), + value: R(e), + illegal: !1, + top: h, + }; + return n.emitter.addText(e), n; + })(e), + r = t; + return ( + n + .filter(T) + .filter(I) + .forEach(function (n) { + var a = m(n, e, !1); + (a.language = n), + a.relevance > r.relevance && (r = a), + a.relevance > t.relevance && ((r = t), (t = a)); + }), + r.language && (t.second_best = r), + t + ); + } + function x(e) { + return f.tabReplace || f.useBR + ? e.replace(c, (e) => + "\n" === e + ? f.useBR + ? "
" + : e + : f.tabReplace + ? e.replace(/\t/g, f.tabReplace) + : e + ) + : e; + } + function E(e) { + let n = null; + const t = (function (e) { + var n = e.className + " "; + n += e.parentNode ? e.parentNode.className : ""; + const t = f.languageDetectRe.exec(n); + if (t) { + var r = T(t[1]); + return ( + r || + (console.warn(g.replace("{}", t[1])), + console.warn( + "Falling back to no-highlight mode for this block.", + e + )), + r ? t[1] : "no-highlight" + ); + } + return n.split(/\s+/).find((e) => p(e) || T(e)); + })(e); + if (p(t)) return; + S("before:highlightBlock", { block: e, language: t }), + f.useBR + ? ((n = document.createElement("div")).innerHTML = e.innerHTML + .replace(/\n/g, "") + .replace(//g, "\n")) + : (n = e); + const r = n.textContent, + a = t ? b(t, r, !0) : v(r), + i = k(n); + if (i.length) { + const e = document.createElement("div"); + (e.innerHTML = a.value), (a.value = O(i, k(e), r)); + } + (a.value = x(a.value)), + S("after:highlightBlock", { block: e, result: a }), + (e.innerHTML = a.value), + (e.className = (function (e, n, t) { + var r = n ? s[n] : t, + a = [e.trim()]; + return ( + e.match(/\bhljs\b/) || a.push("hljs"), + e.includes(r) || a.push(r), + a.join(" ").trim() + ); + })(e.className, t, a.language)), + (e.result = { + language: a.language, + re: a.relevance, + relavance: a.relevance, + }), + a.second_best && + (e.second_best = { + language: a.second_best.language, + re: a.second_best.relevance, + relavance: a.second_best.relevance, + }); + } + const N = () => { + if (!N.called) { + N.called = !0; + var e = document.querySelectorAll("pre code"); + a.forEach.call(e, E); + } + }; + function T(e) { + return (e = (e || "").toLowerCase()), i[e] || i[s[e]]; + } + function A(e, { languageName: n }) { + "string" == typeof e && (e = [e]), + e.forEach((e) => { + s[e] = n; + }); + } + function I(e) { + var n = T(e); + return n && !n.disableAutodetect; + } + function S(e, n) { + var t = e; + o.forEach(function (e) { + e[t] && e[t](n); + }); + } + Object.assign(t, { + highlight: b, + highlightAuto: v, + fixMarkup: x, + highlightBlock: E, + configure: function (e) { + f = y(f, e); + }, + initHighlighting: N, + initHighlightingOnLoad: function () { + window.addEventListener("DOMContentLoaded", N, !1); + }, + registerLanguage: function (e, n) { + var r = null; + try { + r = n(t); + } catch (n) { + if ( + (console.error( + "Language definition for '{}' could not be registered.".replace( + "{}", + e + ) + ), + !l) + ) + throw n; + console.error(n), (r = h); + } + r.name || (r.name = e), + (i[e] = r), + (r.rawDefinition = n.bind(null, t)), + r.aliases && A(r.aliases, { languageName: e }); + }, + listLanguages: function () { + return Object.keys(i); + }, + getLanguage: T, + registerAliases: A, + requireLanguage: function (e) { + var n = T(e); + if (n) return n; + throw Error( + "The '{}' language is required, but not loaded.".replace("{}", e) + ); + }, + autoDetection: I, + inherit: y, + addPlugin: function (e) { + o.push(e); + }, + }), + (t.debugMode = function () { + l = !1; + }), + (t.safeMode = function () { + l = !0; + }), + (t.versionString = "10.1.1"); + for (const n in _) "object" == typeof _[n] && e(_[n]); + return Object.assign(t, _), t; + })({}); +})(); +"object" == typeof exports && + "undefined" != typeof module && + (module.exports = hljs); +hljs.registerLanguage( + "css", + (function () { + "use strict"; + return function (e) { + var n = { + begin: /(?:[A-Z\_\.\-]+|--[a-zA-Z0-9_-]+)\s*:/, + returnBegin: !0, + end: ";", + endsWithParent: !0, + contains: [ + { + className: "attribute", + begin: /\S/, + end: ":", + excludeEnd: !0, + starts: { + endsWithParent: !0, + excludeEnd: !0, + contains: [ + { + begin: /[\w-]+\(/, + returnBegin: !0, + contains: [ + { className: "built_in", begin: /[\w-]+/ }, + { + begin: /\(/, + end: /\)/, + contains: [ + e.APOS_STRING_MODE, + e.QUOTE_STRING_MODE, + e.CSS_NUMBER_MODE, + ], + }, + ], + }, + e.CSS_NUMBER_MODE, + e.QUOTE_STRING_MODE, + e.APOS_STRING_MODE, + e.C_BLOCK_COMMENT_MODE, + { className: "number", begin: "#[0-9A-Fa-f]+" }, + { className: "meta", begin: "!important" }, + ], + }, + }, + ], + }; + return { + name: "CSS", + case_insensitive: !0, + illegal: /[=\/|'\$]/, + contains: [ + e.C_BLOCK_COMMENT_MODE, + { className: "selector-id", begin: /#[A-Za-z0-9_-]+/ }, + { className: "selector-class", begin: /\.[A-Za-z0-9_-]+/ }, + { + className: "selector-attr", + begin: /\[/, + end: /\]/, + illegal: "$", + contains: [e.APOS_STRING_MODE, e.QUOTE_STRING_MODE], + }, + { + className: "selector-pseudo", + begin: /:(:)?[a-zA-Z0-9\_\-\+\(\)"'.]+/, + }, + { + begin: "@(page|font-face)", + lexemes: "@[a-z-]+", + keywords: "@page @font-face", + }, + { + begin: "@", + end: "[{;]", + illegal: /:/, + returnBegin: !0, + contains: [ + { className: "keyword", begin: /@\-?\w[\w]*(\-\w+)*/ }, + { + begin: /\s/, + endsWithParent: !0, + excludeEnd: !0, + relevance: 0, + keywords: "and or not only", + contains: [ + { begin: /[a-z-]+:/, className: "attribute" }, + e.APOS_STRING_MODE, + e.QUOTE_STRING_MODE, + e.CSS_NUMBER_MODE, + ], + }, + ], + }, + { + className: "selector-tag", + begin: "[a-zA-Z-][a-zA-Z0-9_-]*", + relevance: 0, + }, + { + begin: "{", + end: "}", + illegal: /\S/, + contains: [e.C_BLOCK_COMMENT_MODE, n], + }, + ], + }; + }; + })() +); +hljs.registerLanguage( + "json", + (function () { + "use strict"; + return function (n) { + var e = { literal: "true false null" }, + i = [n.C_LINE_COMMENT_MODE, n.C_BLOCK_COMMENT_MODE], + t = [n.QUOTE_STRING_MODE, n.C_NUMBER_MODE], + a = { + end: ",", + endsWithParent: !0, + excludeEnd: !0, + contains: t, + keywords: e, + }, + l = { + begin: "{", + end: "}", + contains: [ + { + className: "attr", + begin: /"/, + end: /"/, + contains: [n.BACKSLASH_ESCAPE], + illegal: "\\n", + }, + n.inherit(a, { begin: /:/ }), + ].concat(i), + illegal: "\\S", + }, + s = { + begin: "\\[", + end: "\\]", + contains: [n.inherit(a)], + illegal: "\\S", + }; + return ( + t.push(l, s), + i.forEach(function (n) { + t.push(n); + }), + { name: "JSON", contains: t, keywords: e, illegal: "\\S" } + ); + }; + })() +); +hljs.registerLanguage( + "markdown", + (function () { + "use strict"; + return function (n) { + const e = { begin: "<", end: ">", subLanguage: "xml", relevance: 0 }, + a = { + begin: "\\[.+?\\][\\(\\[].*?[\\)\\]]", + returnBegin: !0, + contains: [ + { + className: "string", + begin: "\\[", + end: "\\]", + excludeBegin: !0, + returnEnd: !0, + relevance: 0, + }, + { + className: "link", + begin: "\\]\\(", + end: "\\)", + excludeBegin: !0, + excludeEnd: !0, + }, + { + className: "symbol", + begin: "\\]\\[", + end: "\\]", + excludeBegin: !0, + excludeEnd: !0, + }, + ], + relevance: 10, + }, + i = { + className: "strong", + contains: [], + variants: [ + { begin: /_{2}/, end: /_{2}/ }, + { begin: /\*{2}/, end: /\*{2}/ }, + ], + }, + s = { + className: "emphasis", + contains: [], + variants: [ + { begin: /\*(?!\*)/, end: /\*/ }, + { begin: /_(?!_)/, end: /_/, relevance: 0 }, + ], + }; + i.contains.push(s), s.contains.push(i); + var c = [e, a]; + return ( + (i.contains = i.contains.concat(c)), + (s.contains = s.contains.concat(c)), + { + name: "Markdown", + aliases: ["md", "mkdown", "mkd"], + contains: [ + { + className: "section", + variants: [ + { begin: "^#{1,6}", end: "$", contains: (c = c.concat(i, s)) }, + { + begin: "(?=^.+?\\n[=-]{2,}$)", + contains: [ + { begin: "^[=-]*$" }, + { begin: "^", end: "\\n", contains: c }, + ], + }, + ], + }, + e, + { + className: "bullet", + begin: "^[ \t]*([*+-]|(\\d+\\.))(?=\\s+)", + end: "\\s+", + excludeEnd: !0, + }, + i, + s, + { className: "quote", begin: "^>\\s+", contains: c, end: "$" }, + { + className: "code", + variants: [ + { begin: "(`{3,})(.|\\n)*?\\1`*[ ]*" }, + { begin: "(~{3,})(.|\\n)*?\\1~*[ ]*" }, + { begin: "```", end: "```+[ ]*$" }, + { begin: "~~~", end: "~~~+[ ]*$" }, + { begin: "`.+?`" }, + { + begin: "(?=^( {4}|\\t))", + contains: [{ begin: "^( {4}|\\t)", end: "(\\n)$" }], + relevance: 0, + }, + ], + }, + { begin: "^[-\\*]{3,}", end: "$" }, + a, + { + begin: /^\[[^\n]+\]:/, + returnBegin: !0, + contains: [ + { + className: "symbol", + begin: /\[/, + end: /\]/, + excludeBegin: !0, + excludeEnd: !0, + }, + { + className: "link", + begin: /:\s*/, + end: /$/, + excludeBegin: !0, + }, + ], + }, + ], + } + ); + }; + })() +); +hljs.registerLanguage( + "plaintext", + (function () { + "use strict"; + return function (t) { + return { + name: "Plain text", + aliases: ["text", "txt"], + disableAutodetect: !0, + }; + }; + })() +); +hljs.registerLanguage( + "python", + (function () { + "use strict"; + return function (e) { + var n = { + keyword: + "and elif is global as in if from raise for except finally print import pass return exec else break not with class assert yield try while continue del or def lambda async await nonlocal|10", + built_in: "Ellipsis NotImplemented", + literal: "False None True", + }, + a = { className: "meta", begin: /^(>>>|\.\.\.) / }, + i = { + className: "subst", + begin: /\{/, + end: /\}/, + keywords: n, + illegal: /#/, + }, + s = { begin: /\{\{/, relevance: 0 }, + r = { + className: "string", + contains: [e.BACKSLASH_ESCAPE], + variants: [ + { + begin: /(u|b)?r?'''/, + end: /'''/, + contains: [e.BACKSLASH_ESCAPE, a], + relevance: 10, + }, + { + begin: /(u|b)?r?"""/, + end: /"""/, + contains: [e.BACKSLASH_ESCAPE, a], + relevance: 10, + }, + { + begin: /(fr|rf|f)'''/, + end: /'''/, + contains: [e.BACKSLASH_ESCAPE, a, s, i], + }, + { + begin: /(fr|rf|f)"""/, + end: /"""/, + contains: [e.BACKSLASH_ESCAPE, a, s, i], + }, + { begin: /(u|r|ur)'/, end: /'/, relevance: 10 }, + { begin: /(u|r|ur)"/, end: /"/, relevance: 10 }, + { begin: /(b|br)'/, end: /'/ }, + { begin: /(b|br)"/, end: /"/ }, + { + begin: /(fr|rf|f)'/, + end: /'/, + contains: [e.BACKSLASH_ESCAPE, s, i], + }, + { + begin: /(fr|rf|f)"/, + end: /"/, + contains: [e.BACKSLASH_ESCAPE, s, i], + }, + e.APOS_STRING_MODE, + e.QUOTE_STRING_MODE, + ], + }, + l = { + className: "number", + relevance: 0, + variants: [ + { begin: e.BINARY_NUMBER_RE + "[lLjJ]?" }, + { begin: "\\b(0o[0-7]+)[lLjJ]?" }, + { begin: e.C_NUMBER_RE + "[lLjJ]?" }, + ], + }, + t = { + className: "params", + variants: [ + { begin: /\(\s*\)/, skip: !0, className: null }, + { + begin: /\(/, + end: /\)/, + excludeBegin: !0, + excludeEnd: !0, + contains: ["self", a, l, r, e.HASH_COMMENT_MODE], + }, + ], + }; + return ( + (i.contains = [r, l, a]), + { + name: "Python", + aliases: ["py", "gyp", "ipython"], + keywords: n, + illegal: /(<\/|->|\?)|=>/, + contains: [ + a, + l, + { beginKeywords: "if", relevance: 0 }, + r, + e.HASH_COMMENT_MODE, + { + variants: [ + { className: "function", beginKeywords: "def" }, + { className: "class", beginKeywords: "class" }, + ], + end: /:/, + illegal: /[${=;\n,]/, + contains: [ + e.UNDERSCORE_TITLE_MODE, + t, + { begin: /->/, endsWithParent: !0, keywords: "None" }, + ], + }, + { className: "meta", begin: /^[\t ]*@/, end: /$/ }, + { begin: /\b(print|exec)\(/ }, + ], + } + ); + }; + })() +); +hljs.registerLanguage( + "rust", + (function () { + "use strict"; + return function (e) { + var n = "([ui](8|16|32|64|128|size)|f(32|64))?", + t = + "drop i8 i16 i32 i64 i128 isize u8 u16 u32 u64 u128 usize f32 f64 str char bool Box Option Result String Vec Copy Send Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert! assert_eq! bitflags! bytes! cfg! col! concat! concat_idents! debug_assert! debug_assert_eq! env! panic! file! format! format_args! include_bin! include_str! line! local_data_key! module_path! option_env! print! println! select! stringify! try! unimplemented! unreachable! vec! write! writeln! macro_rules! assert_ne! debug_assert_ne!"; + return { + name: "Rust", + aliases: ["rs"], + keywords: { + $pattern: e.IDENT_RE + "!?", + keyword: + "abstract as async await become box break const continue crate do dyn else enum extern false final fn for if impl in let loop macro match mod move mut override priv pub ref return self Self static struct super trait true try type typeof unsafe unsized use virtual where while yield", + literal: "true false Some None Ok Err", + built_in: t, + }, + illegal: "" }, + ], + }; + }; + })() +); +hljs.registerLanguage( + "shell", + (function () { + "use strict"; + return function (s) { + return { + name: "Shell Session", + aliases: ["console"], + contains: [ + { + className: "meta", + begin: "^\\s{0,3}[/\\w\\d\\[\\]()@-]*[>%$#]", + starts: { end: "$", subLanguage: "bash" }, + }, + ], + }; + }; + })() +); +hljs.registerLanguage( + "typescript", + (function () { + "use strict"; + const e = [ + "as", + "in", + "of", + "if", + "for", + "while", + "finally", + "var", + "new", + "function", + "do", + "return", + "void", + "else", + "break", + "catch", + "instanceof", + "with", + "throw", + "case", + "default", + "try", + "switch", + "continue", + "typeof", + "delete", + "let", + "yield", + "const", + "class", + "debugger", + "async", + "await", + "static", + "import", + "from", + "export", + "extends", + ], + n = ["true", "false", "null", "undefined", "NaN", "Infinity"], + a = [].concat( + [ + "setInterval", + "setTimeout", + "clearInterval", + "clearTimeout", + "require", + "exports", + "eval", + "isFinite", + "isNaN", + "parseFloat", + "parseInt", + "decodeURI", + "decodeURIComponent", + "encodeURI", + "encodeURIComponent", + "escape", + "unescape", + ], + [ + "arguments", + "this", + "super", + "console", + "window", + "document", + "localStorage", + "module", + "global", + ], + [ + "Intl", + "DataView", + "Number", + "Math", + "Date", + "String", + "RegExp", + "Object", + "Function", + "Boolean", + "Error", + "Symbol", + "Set", + "Map", + "WeakSet", + "WeakMap", + "Proxy", + "Reflect", + "JSON", + "Promise", + "Float64Array", + "Int16Array", + "Int32Array", + "Int8Array", + "Uint16Array", + "Uint32Array", + "Float32Array", + "Array", + "Uint8Array", + "Uint8ClampedArray", + "ArrayBuffer", + ], + [ + "EvalError", + "InternalError", + "RangeError", + "ReferenceError", + "SyntaxError", + "TypeError", + "URIError", + ] + ); + return function (r) { + var t = { + $pattern: "[A-Za-z$_][0-9A-Za-z$_]*", + keyword: e + .concat([ + "type", + "namespace", + "typedef", + "interface", + "public", + "private", + "protected", + "implements", + "declare", + "abstract", + "readonly", + ]) + .join(" "), + literal: n.join(" "), + built_in: a + .concat([ + "any", + "void", + "number", + "boolean", + "string", + "object", + "never", + "enum", + ]) + .join(" "), + }, + s = { className: "meta", begin: "@[A-Za-z$_][0-9A-Za-z$_]*" }, + i = { + className: "number", + variants: [ + { begin: "\\b(0[bB][01]+)n?" }, + { begin: "\\b(0[oO][0-7]+)n?" }, + { begin: r.C_NUMBER_RE + "n?" }, + ], + relevance: 0, + }, + o = { + className: "subst", + begin: "\\$\\{", + end: "\\}", + keywords: t, + contains: [], + }, + c = { + begin: "html`", + end: "", + starts: { + end: "`", + returnEnd: !1, + contains: [r.BACKSLASH_ESCAPE, o], + subLanguage: "xml", + }, + }, + l = { + begin: "css`", + end: "", + starts: { + end: "`", + returnEnd: !1, + contains: [r.BACKSLASH_ESCAPE, o], + subLanguage: "css", + }, + }, + E = { + className: "string", + begin: "`", + end: "`", + contains: [r.BACKSLASH_ESCAPE, o], + }; + o.contains = [ + r.APOS_STRING_MODE, + r.QUOTE_STRING_MODE, + c, + l, + E, + i, + r.REGEXP_MODE, + ]; + var d = { + begin: "\\(", + end: /\)/, + keywords: t, + contains: [ + "self", + r.QUOTE_STRING_MODE, + r.APOS_STRING_MODE, + r.NUMBER_MODE, + ], + }, + u = { + className: "params", + begin: /\(/, + end: /\)/, + excludeBegin: !0, + excludeEnd: !0, + keywords: t, + contains: [r.C_LINE_COMMENT_MODE, r.C_BLOCK_COMMENT_MODE, s, d], + }; + return { + name: "TypeScript", + aliases: ["ts"], + keywords: t, + contains: [ + r.SHEBANG(), + { className: "meta", begin: /^\s*['"]use strict['"]/ }, + r.APOS_STRING_MODE, + r.QUOTE_STRING_MODE, + c, + l, + E, + r.C_LINE_COMMENT_MODE, + r.C_BLOCK_COMMENT_MODE, + i, + { + begin: "(" + r.RE_STARTERS_RE + "|\\b(case|return|throw)\\b)\\s*", + keywords: "return throw case", + contains: [ + r.C_LINE_COMMENT_MODE, + r.C_BLOCK_COMMENT_MODE, + r.REGEXP_MODE, + { + className: "function", + begin: + "(\\([^(]*(\\([^(]*(\\([^(]*\\))?\\))?\\)|" + + r.UNDERSCORE_IDENT_RE + + ")\\s*=>", + returnBegin: !0, + end: "\\s*=>", + contains: [ + { + className: "params", + variants: [ + { begin: r.UNDERSCORE_IDENT_RE }, + { className: null, begin: /\(\s*\)/, skip: !0 }, + { + begin: /\(/, + end: /\)/, + excludeBegin: !0, + excludeEnd: !0, + keywords: t, + contains: d.contains, + }, + ], + }, + ], + }, + ], + relevance: 0, + }, + { + className: "function", + beginKeywords: "function", + end: /[\{;]/, + excludeEnd: !0, + keywords: t, + contains: [ + "self", + r.inherit(r.TITLE_MODE, { begin: "[A-Za-z$_][0-9A-Za-z$_]*" }), + u, + ], + illegal: /%/, + relevance: 0, + }, + { + beginKeywords: "constructor", + end: /[\{;]/, + excludeEnd: !0, + contains: ["self", u], + }, + { begin: /module\./, keywords: { built_in: "module" }, relevance: 0 }, + { beginKeywords: "module", end: /\{/, excludeEnd: !0 }, + { + beginKeywords: "interface", + end: /\{/, + excludeEnd: !0, + keywords: "interface extends", + }, + { begin: /\$[(.]/ }, + { begin: "\\." + r.IDENT_RE, relevance: 0 }, + s, + d, + ], + }; + }; + })() +); +hljs.registerLanguage( + "yaml", + (function () { + "use strict"; + return function (e) { + var n = "true false yes no null", + a = "[\\w#;/?:@&=+$,.~*\\'()[\\]]+", + s = { + className: "string", + relevance: 0, + variants: [ + { begin: /'/, end: /'/ }, + { begin: /"/, end: /"/ }, + { begin: /\S+/ }, + ], + contains: [ + e.BACKSLASH_ESCAPE, + { + className: "template-variable", + variants: [ + { begin: "{{", end: "}}" }, + { begin: "%{", end: "}" }, + ], + }, + ], + }, + i = e.inherit(s, { + variants: [ + { begin: /'/, end: /'/ }, + { begin: /"/, end: /"/ }, + { begin: /[^\s,{}[\]]+/ }, + ], + }), + l = { + end: ",", + endsWithParent: !0, + excludeEnd: !0, + contains: [], + keywords: n, + relevance: 0, + }, + t = { + begin: "{", + end: "}", + contains: [l], + illegal: "\\n", + relevance: 0, + }, + g = { + begin: "\\[", + end: "\\]", + contains: [l], + illegal: "\\n", + relevance: 0, + }, + b = [ + { + className: "attr", + variants: [ + { begin: "\\w[\\w :\\/.-]*:(?=[ \t]|$)" }, + { begin: '"\\w[\\w :\\/.-]*":(?=[ \t]|$)' }, + { begin: "'\\w[\\w :\\/.-]*':(?=[ \t]|$)" }, + ], + }, + { className: "meta", begin: "^---s*$", relevance: 10 }, + { + className: "string", + begin: "[\\|>]([0-9]?[+-])?[ ]*\\n( *)[\\S ]+\\n(\\2[\\S ]+\\n?)*", + }, + { + begin: "<%[%=-]?", + end: "[%-]?%>", + subLanguage: "ruby", + excludeBegin: !0, + excludeEnd: !0, + relevance: 0, + }, + { className: "type", begin: "!\\w+!" + a }, + { className: "type", begin: "!<" + a + ">" }, + { className: "type", begin: "!" + a }, + { className: "type", begin: "!!" + a }, + { className: "meta", begin: "&" + e.UNDERSCORE_IDENT_RE + "$" }, + { className: "meta", begin: "\\*" + e.UNDERSCORE_IDENT_RE + "$" }, + { className: "bullet", begin: "\\-(?=[ ]|$)", relevance: 0 }, + e.HASH_COMMENT_MODE, + { beginKeywords: n, keywords: { literal: n } }, + { + className: "number", + begin: + "\\b[0-9]{4}(-[0-9][0-9]){0,2}([Tt \\t][0-9][0-9]?(:[0-9][0-9]){2})?(\\.[0-9]*)?([ \\t])*(Z|[-+][0-9][0-9]?(:[0-9][0-9])?)?\\b", + }, + { className: "number", begin: e.C_NUMBER_RE + "\\b" }, + t, + g, + s, + ], + c = [...b]; + return ( + c.pop(), + c.push(i), + (l.contains = c), + { + name: "YAML", + case_insensitive: !0, + aliases: ["yml", "YAML"], + contains: b, + } + ); + }; + })() +); +hljs.registerLanguage( + "cairo", + (function () { + "use strict"; + return function (e) { + var n = "([ui](8|16|32|64|128|size)|f(32|64))?", + t = + "Copy Send Serde Sized Sync Drop Fn FnMut FnOnce ToOwned Clone Debug PartialEq PartialOrd Eq Ord AsRef AsMut Into From Default Iterator Extend IntoIterator DoubleEndedIterator ExactSizeIterator SliceConcatExt ToString assert assert! assert_eq! assert_ne! assert_lt! assert_le! assert_gt! assert_ge! format! write! writeln! get_dep_component! get_dep_component_mut! component! consteval_int! array! panic! print! println!"; + return { + name: "Cairo", + aliases: ["cairo"], + keywords: { + $pattern: e.IDENT_RE + "!?", + keyword: + "as break const continue else enum extern false fn if impl implicits let loop match mod mut nopanic of pub ref return self struct super trait true type use while", + literal: "true false", + built_in: t, + }, + illegal: "" }, + ], + }; + }; + })() +); From 7e8358eba16852b771b34b2fecca7ef8ac7cecca Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 24 Oct 2024 09:59:17 +0200 Subject: [PATCH 35/36] Expand Constant Product AMM's description (#252) * Expand Constant Product AMM's description * Refactor --------- Co-authored-by: Julio <30329843+julio4@users.noreply.github.com> --- src/applications/constant-product-amm.md | 28 ++++++++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/src/applications/constant-product-amm.md b/src/applications/constant-product-amm.md index c5adb16e..fc8a115e 100644 --- a/src/applications/constant-product-amm.md +++ b/src/applications/constant-product-amm.md @@ -2,6 +2,34 @@ This is the Cairo adaptation of the [Solidity by Example - Constant Product AMM](https://solidity-by-example.org/defi/constant-product-amm/). +In this contract, we implement a simple Automated Market Maker (AMM) following +the **constant product formula**: \\( x \cdot y = k \\). This formula ensures +that the product of the two token reserves (`x` and `y` representing the tokens +being swapper) remains constant, regardless of trades. Here, we provide +liquidity pools that allow users to trade between two tokens or add and remove +liquidity from the pool. + +## Key Concepts + +1. **approve() before swap or adding liquidity**: + Before interacting with the AMM (whether through swaps or adding liquidity), + the user must approve the contract to spend their tokens. This is done by + calling the `approve()` function on the ERC20 token contracts, allowing the + AMM to transfer the required tokens on behalf of the user. + +2. **Constant Product Formula for Swaps**: + The swap function operates based on the constant product formula \\( x \cdot + y = k \\), where `x` and `y` are the token reserves. When a user swaps one + token for another, the product of the reserves remains constant, which + determines how much of the other token the user will receive. + +3. **Shares and Token Ratios for Liquidity**: + When adding liquidity, users provide both tokens in the ratio of the current + reserves. The number of shares (liquidity tokens) the user receives + represents their contribution to the pool. Similarly, when removing + liquidity, users receive back tokens proportional to the number of shares + they burn. + ```cairo {{#include ../../listings/applications/constant_product_amm/src/contracts.cairo:ConstantProductAmmContract}} ``` From 024cd079b5315be20295e082fe0cac0f5693c7e6 Mon Sep 17 00:00:00 2001 From: Nenad Date: Thu, 24 Oct 2024 09:59:55 +0200 Subject: [PATCH 36/36] Minor _Scarb.toml_ refactors (#251) * Set one common edition version in workspace Scarb.toml * move snforge_std to dev-deps * Remove [lib] from coin_flip and simple_storage_starknetjs * rebuild projects * remove casm attr from scarb.toml * Update foundry to 0.31.0 in tool-versions * Set workspace cairo_test in mappings * Add missing cairo_test.workspace attrbs to Scarb.tomls * Revert snforge_std to 0.30.0 * Revert "Add missing cairo_test.workspace attrbs to Scarb.tomls" This reverts commit 7a9131dd2ef35ec1457f4829dbb3b9dd0a6b7439. --- .tool-versions | 2 +- CONTRIBUTING.md | 2 +- Scarb.toml | 1 + listings/advanced-concepts/ecdsa_verification/Scarb.toml | 2 +- .../advanced-concepts/hash_solidity_compatible/Scarb.toml | 2 +- listings/advanced-concepts/hash_trait/Scarb.toml | 2 +- listings/advanced-concepts/library_calls/Scarb.toml | 2 +- listings/advanced-concepts/simple_account/Scarb.toml | 2 +- listings/advanced-concepts/store_using_packing/Scarb.toml | 2 +- listings/advanced-concepts/struct_as_mapping_key/Scarb.toml | 2 +- listings/advanced-concepts/write_to_any_slot/Scarb.toml | 2 +- listings/applications/advanced_factory/Scarb.toml | 5 ++--- listings/applications/coin_flip/Scarb.toml | 6 ++---- listings/applications/components/Scarb.toml | 2 +- listings/applications/components_dependencies/Scarb.toml | 2 +- listings/applications/constant_product_amm/Scarb.toml | 2 +- listings/applications/crowdfunding/Scarb.toml | 4 ++-- listings/applications/erc20/Scarb.toml | 2 +- listings/applications/merkle_tree/Scarb.toml | 2 +- listings/applications/nft_dutch_auction/Scarb.toml | 4 ++-- listings/applications/simple_storage_starknetjs/Scarb.toml | 4 +--- listings/applications/simple_vault/Scarb.toml | 2 +- listings/applications/staking/Scarb.toml | 2 +- listings/applications/timelock/Scarb.toml | 5 ++--- listings/applications/upgradeable_contract/Scarb.toml | 2 +- listings/getting-started/bytearray/Scarb.toml | 2 +- listings/getting-started/cairo_cheatsheet/Scarb.toml | 2 +- listings/getting-started/calling_other_contracts/Scarb.toml | 2 +- listings/getting-started/constructor/Scarb.toml | 2 +- listings/getting-started/counter/Scarb.toml | 2 +- listings/getting-started/custom_type_serde/Scarb.toml | 2 +- listings/getting-started/errors/Scarb.toml | 2 +- listings/getting-started/events/Scarb.toml | 2 +- listings/getting-started/factory/Scarb.toml | 2 +- listings/getting-started/interfaces_traits/Scarb.toml | 2 +- listings/getting-started/mappings/Scarb.toml | 4 ++-- listings/getting-started/storage/Scarb.toml | 2 +- listings/getting-started/storing_custom_types/Scarb.toml | 2 +- listings/getting-started/testing_how_to/Scarb.toml | 2 +- listings/getting-started/variables/Scarb.toml | 2 +- listings/getting-started/visibility/Scarb.toml | 2 +- 41 files changed, 47 insertions(+), 52 deletions(-) diff --git a/.tool-versions b/.tool-versions index 120e90d0..aac540e2 100644 --- a/.tool-versions +++ b/.tool-versions @@ -1,2 +1,2 @@ scarb 2.8.2 -starknet-foundry 0.30.0 \ No newline at end of file +starknet-foundry 0.30.0 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 22a64e33..bc9766aa 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -112,7 +112,6 @@ version.workspace = true [dependencies] starknet.workspace = true -snforge_std.workspace = true # Uncomment the following lines if you want to use additional dependencies: # OpenZeppelin: # openzeppelin.workspace = true @@ -122,6 +121,7 @@ snforge_std.workspace = true [dev-dependencies] assert_macros.workspace = true +snforge_std.workspace = true [scripts] test.workspace = true diff --git a/Scarb.toml b/Scarb.toml index 7482a04e..76f4fe96 100644 --- a/Scarb.toml +++ b/Scarb.toml @@ -27,6 +27,7 @@ homepage = "https://www.nethermind.io/" license = "MIT" authors = ["julio4", "msaug"] version = "0.1.0" +edition = "2024_07" [tool] snforge.workspace = true diff --git a/listings/advanced-concepts/ecdsa_verification/Scarb.toml b/listings/advanced-concepts/ecdsa_verification/Scarb.toml index 96ed1b58..8f178961 100644 --- a/listings/advanced-concepts/ecdsa_verification/Scarb.toml +++ b/listings/advanced-concepts/ecdsa_verification/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "ecdsa_verification" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml b/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml index 9f3c6244..601d3988 100644 --- a/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml +++ b/listings/advanced-concepts/hash_solidity_compatible/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "hash_solidity_compatible" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/hash_trait/Scarb.toml b/listings/advanced-concepts/hash_trait/Scarb.toml index 44a44d27..e308acce 100644 --- a/listings/advanced-concepts/hash_trait/Scarb.toml +++ b/listings/advanced-concepts/hash_trait/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "hash_trait" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/library_calls/Scarb.toml b/listings/advanced-concepts/library_calls/Scarb.toml index e077de16..62897c98 100644 --- a/listings/advanced-concepts/library_calls/Scarb.toml +++ b/listings/advanced-concepts/library_calls/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "library_calls" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/simple_account/Scarb.toml b/listings/advanced-concepts/simple_account/Scarb.toml index 7454c359..4c2912fd 100644 --- a/listings/advanced-concepts/simple_account/Scarb.toml +++ b/listings/advanced-concepts/simple_account/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "simple_account" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/store_using_packing/Scarb.toml b/listings/advanced-concepts/store_using_packing/Scarb.toml index 2625aca3..66797103 100644 --- a/listings/advanced-concepts/store_using_packing/Scarb.toml +++ b/listings/advanced-concepts/store_using_packing/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "store_using_packing" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml b/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml index 8bfd3839..65c00ffa 100644 --- a/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml +++ b/listings/advanced-concepts/struct_as_mapping_key/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "struct_as_mapping_key" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/advanced-concepts/write_to_any_slot/Scarb.toml b/listings/advanced-concepts/write_to_any_slot/Scarb.toml index cbad5243..2465289e 100644 --- a/listings/advanced-concepts/write_to_any_slot/Scarb.toml +++ b/listings/advanced-concepts/write_to_any_slot/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "write_to_any_slot" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/advanced_factory/Scarb.toml b/listings/applications/advanced_factory/Scarb.toml index 825a4654..7a654c03 100644 --- a/listings/applications/advanced_factory/Scarb.toml +++ b/listings/applications/advanced_factory/Scarb.toml @@ -1,20 +1,19 @@ [package] name = "advanced_factory" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true components.workspace = true -snforge_std.workspace = true crowdfunding = { path = "../crowdfunding" } [dev-dependencies] assert_macros.workspace = true +snforge_std.workspace = true [scripts] test.workspace = true [[target.starknet-contract]] -casm = true build-external-contracts = ["crowdfunding::campaign::Campaign"] diff --git a/listings/applications/coin_flip/Scarb.toml b/listings/applications/coin_flip/Scarb.toml index c1ae15f7..c59791c8 100644 --- a/listings/applications/coin_flip/Scarb.toml +++ b/listings/applications/coin_flip/Scarb.toml @@ -1,18 +1,16 @@ [package] name = "coin_flip" version.workspace = true -edition = "2024_07" - -[lib] +edition.workspace = true [dependencies] starknet.workspace = true openzeppelin.workspace = true pragma_lib.workspace = true -snforge_std.workspace = true [dev-dependencies] assert_macros.workspace = true +snforge_std.workspace = true [scripts] test.workspace = true diff --git a/listings/applications/components/Scarb.toml b/listings/applications/components/Scarb.toml index afa1fd51..1ba6df9f 100644 --- a/listings/applications/components/Scarb.toml +++ b/listings/applications/components/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "components" version.workspace = true -edition = "2024_07" +edition.workspace = true [lib] diff --git a/listings/applications/components_dependencies/Scarb.toml b/listings/applications/components_dependencies/Scarb.toml index 27d39111..3d0f6a52 100644 --- a/listings/applications/components_dependencies/Scarb.toml +++ b/listings/applications/components_dependencies/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "components_dependencies" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/constant_product_amm/Scarb.toml b/listings/applications/constant_product_amm/Scarb.toml index 4a798451..0d57dce7 100644 --- a/listings/applications/constant_product_amm/Scarb.toml +++ b/listings/applications/constant_product_amm/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "constant_product_amm" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/crowdfunding/Scarb.toml b/listings/applications/crowdfunding/Scarb.toml index 744ae4d2..ae5131d4 100644 --- a/listings/applications/crowdfunding/Scarb.toml +++ b/listings/applications/crowdfunding/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "crowdfunding" version.workspace = true -edition = "2024_07" +edition.workspace = true [lib] @@ -9,10 +9,10 @@ edition = "2024_07" starknet.workspace = true openzeppelin.workspace = true components.workspace = true -snforge_std.workspace = true [dev-dependencies] assert_macros.workspace = true +snforge_std.workspace = true [scripts] test.workspace = true diff --git a/listings/applications/erc20/Scarb.toml b/listings/applications/erc20/Scarb.toml index 81a90227..519aea86 100644 --- a/listings/applications/erc20/Scarb.toml +++ b/listings/applications/erc20/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "erc20" version.workspace = true -edition = "2024_07" +edition.workspace = true [lib] diff --git a/listings/applications/merkle_tree/Scarb.toml b/listings/applications/merkle_tree/Scarb.toml index bba5cb58..d05dd121 100644 --- a/listings/applications/merkle_tree/Scarb.toml +++ b/listings/applications/merkle_tree/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "merkle_tree" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/nft_dutch_auction/Scarb.toml b/listings/applications/nft_dutch_auction/Scarb.toml index fea2ffae..3fdc75e1 100644 --- a/listings/applications/nft_dutch_auction/Scarb.toml +++ b/listings/applications/nft_dutch_auction/Scarb.toml @@ -1,15 +1,15 @@ [package] name = "nft_dutch_auction" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] erc20 = { path = "../erc20" } starknet.workspace = true -snforge_std.workspace = true [dev-dependencies] assert_macros.workspace = true +snforge_std.workspace = true [scripts] test.workspace = true diff --git a/listings/applications/simple_storage_starknetjs/Scarb.toml b/listings/applications/simple_storage_starknetjs/Scarb.toml index 56c2d211..ea490c2c 100644 --- a/listings/applications/simple_storage_starknetjs/Scarb.toml +++ b/listings/applications/simple_storage_starknetjs/Scarb.toml @@ -1,9 +1,7 @@ [package] name = "simple_storage" version.workspace = true -edition = "2024_07" - -[lib] +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/simple_vault/Scarb.toml b/listings/applications/simple_vault/Scarb.toml index 11d2642c..d96a0664 100644 --- a/listings/applications/simple_vault/Scarb.toml +++ b/listings/applications/simple_vault/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "simple_vault" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/staking/Scarb.toml b/listings/applications/staking/Scarb.toml index 93510593..8479363a 100644 --- a/listings/applications/staking/Scarb.toml +++ b/listings/applications/staking/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "staking" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/applications/timelock/Scarb.toml b/listings/applications/timelock/Scarb.toml index eed924b6..9ccf8c4e 100644 --- a/listings/applications/timelock/Scarb.toml +++ b/listings/applications/timelock/Scarb.toml @@ -1,19 +1,18 @@ [package] name = "timelock" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true -snforge_std.workspace = true openzeppelin.workspace = true components.workspace = true [dev-dependencies] assert_macros.workspace = true +snforge_std.workspace = true [scripts] test.workspace = true [[target.starknet-contract]] -casm = true diff --git a/listings/applications/upgradeable_contract/Scarb.toml b/listings/applications/upgradeable_contract/Scarb.toml index c4dcec0d..f443c52d 100644 --- a/listings/applications/upgradeable_contract/Scarb.toml +++ b/listings/applications/upgradeable_contract/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "upgradeable_contract" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/bytearray/Scarb.toml b/listings/getting-started/bytearray/Scarb.toml index 21e61cdd..0ed5aa06 100644 --- a/listings/getting-started/bytearray/Scarb.toml +++ b/listings/getting-started/bytearray/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "bytearray" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/cairo_cheatsheet/Scarb.toml b/listings/getting-started/cairo_cheatsheet/Scarb.toml index b1ec0393..4daa2a61 100644 --- a/listings/getting-started/cairo_cheatsheet/Scarb.toml +++ b/listings/getting-started/cairo_cheatsheet/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "cairo_cheatsheet" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/calling_other_contracts/Scarb.toml b/listings/getting-started/calling_other_contracts/Scarb.toml index f1727dbd..2595a9bd 100644 --- a/listings/getting-started/calling_other_contracts/Scarb.toml +++ b/listings/getting-started/calling_other_contracts/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "calling_other_contracts" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/constructor/Scarb.toml b/listings/getting-started/constructor/Scarb.toml index 8235973e..db71d0e6 100644 --- a/listings/getting-started/constructor/Scarb.toml +++ b/listings/getting-started/constructor/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "constructor" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/counter/Scarb.toml b/listings/getting-started/counter/Scarb.toml index 1a78d653..3979e167 100644 --- a/listings/getting-started/counter/Scarb.toml +++ b/listings/getting-started/counter/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "counter" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/custom_type_serde/Scarb.toml b/listings/getting-started/custom_type_serde/Scarb.toml index 5414b1dd..1a3dfe62 100644 --- a/listings/getting-started/custom_type_serde/Scarb.toml +++ b/listings/getting-started/custom_type_serde/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "custom_type_serde" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/errors/Scarb.toml b/listings/getting-started/errors/Scarb.toml index 2b8cc106..cc12bd89 100644 --- a/listings/getting-started/errors/Scarb.toml +++ b/listings/getting-started/errors/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "errors" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/events/Scarb.toml b/listings/getting-started/events/Scarb.toml index 58d641fa..35c9f343 100644 --- a/listings/getting-started/events/Scarb.toml +++ b/listings/getting-started/events/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "events" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/factory/Scarb.toml b/listings/getting-started/factory/Scarb.toml index 5df9fd4c..bba267f6 100644 --- a/listings/getting-started/factory/Scarb.toml +++ b/listings/getting-started/factory/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "factory" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/interfaces_traits/Scarb.toml b/listings/getting-started/interfaces_traits/Scarb.toml index 94a47b46..0cc1a25d 100644 --- a/listings/getting-started/interfaces_traits/Scarb.toml +++ b/listings/getting-started/interfaces_traits/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "interfaces_traits" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/mappings/Scarb.toml b/listings/getting-started/mappings/Scarb.toml index a1f50323..b5a30f8a 100644 --- a/listings/getting-started/mappings/Scarb.toml +++ b/listings/getting-started/mappings/Scarb.toml @@ -1,13 +1,13 @@ [package] name = "mappings" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true [dev-dependencies] -cairo_test = "2.7.1" +cairo_test.workspace = true [scripts] test.workspace = true diff --git a/listings/getting-started/storage/Scarb.toml b/listings/getting-started/storage/Scarb.toml index caf77195..4bacb1e5 100644 --- a/listings/getting-started/storage/Scarb.toml +++ b/listings/getting-started/storage/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "storage" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/storing_custom_types/Scarb.toml b/listings/getting-started/storing_custom_types/Scarb.toml index 4a949d4f..44f881b8 100644 --- a/listings/getting-started/storing_custom_types/Scarb.toml +++ b/listings/getting-started/storing_custom_types/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "storing_custom_types" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/testing_how_to/Scarb.toml b/listings/getting-started/testing_how_to/Scarb.toml index 5046ee3d..d1e8295c 100644 --- a/listings/getting-started/testing_how_to/Scarb.toml +++ b/listings/getting-started/testing_how_to/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "testing_how_to" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/variables/Scarb.toml b/listings/getting-started/variables/Scarb.toml index 4b6f31b2..59fc6451 100644 --- a/listings/getting-started/variables/Scarb.toml +++ b/listings/getting-started/variables/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "variables" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true diff --git a/listings/getting-started/visibility/Scarb.toml b/listings/getting-started/visibility/Scarb.toml index aeadb2ef..ec43ebaa 100644 --- a/listings/getting-started/visibility/Scarb.toml +++ b/listings/getting-started/visibility/Scarb.toml @@ -1,7 +1,7 @@ [package] name = "visibility" version.workspace = true -edition = "2024_07" +edition.workspace = true [dependencies] starknet.workspace = true