Skip to content

Commit

Permalink
Migrate ERC20Votes to component (#951)
Browse files Browse the repository at this point in the history
* feat: add main logic

* feat: docs

* feat: add Nonces and tests

* docs: finish v1

* feat: update CHANGELOG

* fix: format

* Update docs/modules/ROOT/pages/guides/snip12.adoc

Co-authored-by: Andrew Fleming <[email protected]>

* Update docs/modules/ROOT/pages/guides/snip12.adoc

Co-authored-by: Andrew Fleming <[email protected]>

* Update docs/modules/ROOT/pages/guides/snip12.adoc

Co-authored-by: Andrew Fleming <[email protected]>

* Update docs/modules/ROOT/pages/guides/snip12.adoc

Co-authored-by: Andrew Fleming <[email protected]>

* Update docs/modules/ROOT/pages/guides/snip12.adoc

Co-authored-by: Andrew Fleming <[email protected]>

* feat: apply update reviews

* fix: tests

* fix: linter

* feat: finish main logic

* feat: add tests

* fix: tests

* feat: update CHANGELOG

* refactor: hooks names

* fix: tests

* feat: add overflow events to ERC20

* feat: bump scarb

* feat: set scarb to 2.6.3

* fix: tests

* Update src/utils/structs/checkpoint.cairo

Co-authored-by: Andrew Fleming <[email protected]>

* Update src/tests/presets/test_erc20_votes.cairo

Co-authored-by: Andrew Fleming <[email protected]>

* feat: apply review updates

* feat: apply review updates

* feat: apply review updates

* fix: test

* fix: comment

---------

Co-authored-by: Andrew Fleming <[email protected]>
  • Loading branch information
ericnordelo and andrew-fleming authored Apr 12, 2024
1 parent bb15d2a commit ec99fc3
Show file tree
Hide file tree
Showing 26 changed files with 1,413 additions and 100 deletions.
6 changes: 6 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Added

- before_update and after_update hooks to ERC20Component (#951)
- INSUFFICIENT_BALANCE and INSUFFICIENT_ALLOWANCE errors to ERC20Component (#951)
- ERC20Votes component (#951)

### Changed

- Allow testing utilities to be importable (#963)
Expand Down
1 change: 1 addition & 0 deletions src/governance.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod utils;
1 change: 1 addition & 0 deletions src/governance/utils.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod interfaces;
3 changes: 3 additions & 0 deletions src/governance/utils/interfaces.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
mod votes;

use votes::IVotes;
34 changes: 34 additions & 0 deletions src/governance/utils/interfaces/votes.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
use starknet::ContractAddress;

/// Common interface for Votes-enabled contracts.
#[starknet::interface]
trait IVotes<TState> {
/// Returns the current amount of votes that `account` has.
fn get_votes(self: @TState, account: ContractAddress) -> u256;

/// Returns the amount of votes that `account` had at a specific moment in the past.
fn get_past_votes(self: @TState, account: ContractAddress, timepoint: u64) -> u256;

/// Returns the total supply of votes available at a specific moment in the past.
///
/// NOTE: This value is the sum of all available votes, which is not necessarily the sum of all delegated votes.
/// Votes that have not been delegated are still part of total supply, even though they would not participate in a
/// vote.
fn get_past_total_supply(self: @TState, timepoint: u64) -> u256;

/// Returns the delegate that `account` has chosen.
fn delegates(self: @TState, account: ContractAddress) -> ContractAddress;

/// Delegates votes from the sender to `delegatee`.
fn delegate(ref self: TState, delegatee: ContractAddress);

/// Delegates votes from `delegator` to `delegatee`.
fn delegate_by_sig(
ref self: TState,
delegator: ContractAddress,
delegatee: ContractAddress,
nonce: felt252,
expiry: u64,
signature: Array<felt252>
);
}
1 change: 1 addition & 0 deletions src/lib.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
mod access;
mod account;
mod governance;
mod introspection;
mod presets;
mod security;
Expand Down
2 changes: 1 addition & 1 deletion src/presets/erc20.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
/// set in the constructor.
#[starknet::contract]
mod ERC20 {
use openzeppelin::token::erc20::ERC20Component;
use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;

component!(path: ERC20Component, storage: erc20, event: ERC20Event);
Expand Down
1 change: 1 addition & 0 deletions src/tests/mocks.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ mod account_mocks;
mod erc1155_mocks;
mod erc1155_receiver_mocks;
mod erc20_mocks;
mod erc20_votes_mocks;
mod erc721_mocks;
mod erc721_receiver_mocks;
mod eth_account_mocks;
Expand Down
6 changes: 3 additions & 3 deletions src/tests/mocks/erc20_mocks.cairo
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#[starknet::contract]
mod DualCaseERC20Mock {
use openzeppelin::token::erc20::ERC20Component;
use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;

component!(path: ERC20Component, storage: erc20, event: ERC20Event);
Expand Down Expand Up @@ -41,7 +41,7 @@ mod DualCaseERC20Mock {

#[starknet::contract]
mod SnakeERC20Mock {
use openzeppelin::token::erc20::ERC20Component;
use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;

component!(path: ERC20Component, storage: erc20, event: ERC20Event);
Expand Down Expand Up @@ -80,7 +80,7 @@ mod SnakeERC20Mock {

#[starknet::contract]
mod CamelERC20Mock {
use openzeppelin::token::erc20::ERC20Component;
use openzeppelin::token::erc20::{ERC20Component, ERC20HooksEmptyImpl};
use starknet::ContractAddress;

component!(path: ERC20Component, storage: erc20, event: ERC20Event);
Expand Down
101 changes: 101 additions & 0 deletions src/tests/mocks/erc20_votes_mocks.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
#[starknet::contract]
mod DualCaseERC20VotesMock {
use openzeppelin::token::erc20::ERC20Component;
use openzeppelin::token::erc20::extensions::ERC20VotesComponent::InternalTrait as ERC20VotesInternalTrait;
use openzeppelin::token::erc20::extensions::ERC20VotesComponent;
use openzeppelin::utils::cryptography::nonces::NoncesComponent;
use openzeppelin::utils::cryptography::snip12::SNIP12Metadata;
use starknet::ContractAddress;

component!(path: ERC20VotesComponent, storage: erc20_votes, event: ERC20VotesEvent);
component!(path: ERC20Component, storage: erc20, event: ERC20Event);
component!(path: NoncesComponent, storage: nonces, event: NoncesEvent);

// ERC20Votes
#[abi(embed_v0)]
impl ERC20VotesComponentImpl =
ERC20VotesComponent::ERC20VotesImpl<ContractState>;

// ERC20Mixin
#[abi(embed_v0)]
impl ERC20MixinImpl = ERC20Component::ERC20MixinImpl<ContractState>;
impl InternalImpl = ERC20Component::InternalImpl<ContractState>;

// Nonces
#[abi(embed_v0)]
impl NoncesImpl = NoncesComponent::NoncesImpl<ContractState>;

#[storage]
struct Storage {
#[substorage(v0)]
erc20_votes: ERC20VotesComponent::Storage,
#[substorage(v0)]
erc20: ERC20Component::Storage,
#[substorage(v0)]
nonces: NoncesComponent::Storage
}

#[event]
#[derive(Drop, starknet::Event)]
enum Event {
#[flat]
ERC20VotesEvent: ERC20VotesComponent::Event,
#[flat]
ERC20Event: ERC20Component::Event,
#[flat]
NoncesEvent: NoncesComponent::Event
}

/// Required for hash computation.
impl SNIP12MetadataImpl of SNIP12Metadata {
fn name() -> felt252 {
'DAPP_NAME'
}
fn version() -> felt252 {
'DAPP_VERSION'
}
}

//
// Hooks
//

impl ERC20VotesHooksImpl<
TContractState,
impl ERC20Votes: ERC20VotesComponent::HasComponent<TContractState>,
impl HasComponent: ERC20Component::HasComponent<TContractState>,
+NoncesComponent::HasComponent<TContractState>,
+Drop<TContractState>
> of ERC20Component::ERC20HooksTrait<TContractState> {
fn before_update(
ref self: ERC20Component::ComponentState<TContractState>,
from: ContractAddress,
recipient: ContractAddress,
amount: u256
) {}

fn after_update(
ref self: ERC20Component::ComponentState<TContractState>,
from: ContractAddress,
recipient: ContractAddress,
amount: u256
) {
let mut erc20_votes_component = get_dep_component_mut!(ref self, ERC20Votes);
erc20_votes_component.transfer_voting_units(from, recipient, amount);
}
}

/// Sets the token `name` and `symbol`.
/// Mints `fixed_supply` tokens to `recipient`.
#[constructor]
fn constructor(
ref self: ContractState,
name: ByteArray,
symbol: ByteArray,
fixed_supply: u256,
recipient: ContractAddress
) {
self.erc20.initializer(name, symbol);
self.erc20._mint(recipient, fixed_supply);
}
}
14 changes: 7 additions & 7 deletions src/tests/presets/test_erc20.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -55,15 +55,15 @@ fn test_constructor() {

#[test]
fn test_total_supply() {
let mut dispatcher = setup_dispatcher();
let dispatcher = setup_dispatcher();

assert_eq!(dispatcher.total_supply(), SUPPLY);
assert_eq!(dispatcher.totalSupply(), SUPPLY);
}

#[test]
fn test_balance_of() {
let mut dispatcher = setup_dispatcher();
let dispatcher = setup_dispatcher();

assert_eq!(dispatcher.balance_of(OWNER()), SUPPLY);
assert_eq!(dispatcher.balanceOf(OWNER()), SUPPLY);
Expand Down Expand Up @@ -133,7 +133,7 @@ fn test_transfer() {
}

#[test]
#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))]
#[should_panic(expected: ('ERC20: insufficient balance', 'ENTRYPOINT_FAILED'))]
fn test_transfer_not_enough_balance() {
let mut dispatcher = setup_dispatcher();
testing::set_contract_address(OWNER());
Expand Down Expand Up @@ -196,7 +196,7 @@ fn test_transfer_from_doesnt_consume_infinite_allowance() {
}

#[test]
#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))]
#[should_panic(expected: ('ERC20: insufficient allowance', 'ENTRYPOINT_FAILED'))]
fn test_transfer_from_greater_than_allowance() {
let mut dispatcher = setup_dispatcher();
testing::set_contract_address(OWNER());
Expand All @@ -219,7 +219,7 @@ fn test_transfer_from_to_zero_address() {
}

#[test]
#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))]
#[should_panic(expected: ('ERC20: insufficient allowance', 'ENTRYPOINT_FAILED'))]
fn test_transfer_from_from_zero_address() {
let mut dispatcher = setup_dispatcher();
dispatcher.transfer_from(Zeroable::zero(), RECIPIENT(), VALUE);
Expand Down Expand Up @@ -259,7 +259,7 @@ fn test_transferFrom_doesnt_consume_infinite_allowance() {
}

#[test]
#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))]
#[should_panic(expected: ('ERC20: insufficient allowance', 'ENTRYPOINT_FAILED'))]
fn test_transferFrom_greater_than_allowance() {
let mut dispatcher = setup_dispatcher();
testing::set_contract_address(OWNER());
Expand All @@ -282,7 +282,7 @@ fn test_transferFrom_to_zero_address() {
}

#[test]
#[should_panic(expected: ('u256_sub Overflow', 'ENTRYPOINT_FAILED'))]
#[should_panic(expected: ('ERC20: insufficient allowance', 'ENTRYPOINT_FAILED'))]
fn test_transferFrom_from_zero_address() {
let mut dispatcher = setup_dispatcher();
dispatcher.transferFrom(Zeroable::zero(), RECIPIENT(), VALUE);
Expand Down
1 change: 1 addition & 0 deletions src/tests/token.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -6,5 +6,6 @@ mod test_dual721_receiver;
mod test_erc1155;
mod test_erc1155_receiver;
mod test_erc20;
mod test_erc20_votes;
mod test_erc721;
mod test_erc721_receiver;
Loading

0 comments on commit ec99fc3

Please sign in to comment.