Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions Scarb.lock
Original file line number Diff line number Diff line change
Expand Up @@ -146,15 +146,15 @@ dependencies = [
[[package]]
name = "starkware_utils"
version = "1.0.0"
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=e11de236fe1e36c9556690749f834b6d0e1c4d49#e11de236fe1e36c9556690749f834b6d0e1c4d49"
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=123332b0895ce9116fb2eaa2469739250c3f733d#123332b0895ce9116fb2eaa2469739250c3f733d"
dependencies = [
"openzeppelin",
]

[[package]]
name = "starkware_utils_testing"
version = "1.0.0"
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=e11de236fe1e36c9556690749f834b6d0e1c4d49#e11de236fe1e36c9556690749f834b6d0e1c4d49"
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=123332b0895ce9116fb2eaa2469739250c3f733d#123332b0895ce9116fb2eaa2469739250c3f733d"
dependencies = [
"openzeppelin",
"snforge_std",
Expand Down
4 changes: 2 additions & 2 deletions Scarb.toml
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ snforge_std = "0.49.0"
assert_macros = "2.12.2"
openzeppelin = "2.0.0"
openzeppelin_testing = "4.2.0"
starkware_utils = { git ="https://github.com/starkware-libs/starkware-starknet-utils", rev="e11de236fe1e36c9556690749f834b6d0e1c4d49" }
starkware_utils_testing = { git="https://github.com/starkware-libs/starkware-starknet-utils", rev="e11de236fe1e36c9556690749f834b6d0e1c4d49" }
starkware_utils = { git ="https://github.com/starkware-libs/starkware-starknet-utils", rev="123332b0895ce9116fb2eaa2469739250c3f733d" }
starkware_utils_testing = { git="https://github.com/starkware-libs/starkware-starknet-utils", rev="123332b0895ce9116fb2eaa2469739250c3f733d" }

[workspace.tool.fmt]
sort-module-level-items = true
Expand Down
13 changes: 13 additions & 0 deletions packages/usdc_migration/src/events.cairo
Original file line number Diff line number Diff line change
@@ -1 +1,14 @@
pub mod USDCMigrationEvents {
use starknet::ContractAddress;

#[derive(Drop, starknet::Event, Debug, PartialEq)]
pub struct USDCMigrated {
#[key]
pub user: ContractAddress,
#[key]
pub from_token: ContractAddress,
#[key]
pub to_token: ContractAddress,
pub amount: u256,
}
}
5 changes: 4 additions & 1 deletion packages/usdc_migration/src/interface.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#[starknet::interface]
pub trait IUSDCMigration<T> { //interface
pub trait IUSDCMigration<T> {
/// Exchanges (1:1) `amount` of legacy token for new token.
/// Precondition: Sufficient allowance of legacy token.
fn swap_to_new(ref self: T, amount: u256);
}

#[starknet::interface]
Expand Down
106 changes: 101 additions & 5 deletions packages/usdc_migration/src/tests/test_usdc_migration.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -7,16 +7,29 @@ use openzeppelin::upgrades::interface::{
IUpgradeableSafeDispatcherTrait,
};
use openzeppelin::upgrades::upgradeable::UpgradeableComponent::Errors as UpgradeableErrors;
use snforge_std::{DeclareResultTrait, TokenTrait};
use snforge_std::{DeclareResultTrait, EventSpyTrait, EventsFilterTrait, TokenTrait, spy_events};
use starkware_utils::constants::MAX_U256;
use starkware_utils_testing::test_utils::{assert_panic_with_felt_error, cheat_caller_address_once};
use starkware_utils::erc20::erc20_errors::Erc20Error;
use starkware_utils::errors::Describable;
use starkware_utils_testing::event_test_utils::assert_number_of_events;
use starkware_utils_testing::test_utils::{
assert_expected_event_emitted, assert_panic_with_error, assert_panic_with_felt_error,
cheat_caller_address_once,
};
use usdc_migration::events::USDCMigrationEvents::USDCMigrated;
use usdc_migration::interface::{
IUSDCMigrationConfigDispatcher, IUSDCMigrationConfigDispatcherTrait,
IUSDCMigrationConfigSafeDispatcher, IUSDCMigrationConfigSafeDispatcherTrait,
IUSDCMigrationDispatcher, IUSDCMigrationDispatcherTrait, IUSDCMigrationSafeDispatcher,
IUSDCMigrationSafeDispatcherTrait,
};
use usdc_migration::tests::test_utils::constants::{
INITIAL_CONTRACT_SUPPLY, INITIAL_SUPPLY, LEGACY_THRESHOLD,
};
use usdc_migration::tests::test_utils::{
deploy_usdc_migration, generic_test_fixture, load_contract_address, load_u256, new_user,
supply_contract,
};
use usdc_migration::tests::test_utils::constants::LEGACY_THRESHOLD;
use usdc_migration::tests::test_utils::{deploy_usdc_migration, load_contract_address, load_u256};

#[test]
fn test_constructor() {
let cfg = deploy_usdc_migration();
Expand Down Expand Up @@ -116,3 +129,86 @@ fn test_upgrade_assertions() {
let result = upgradeable_safe_dispatcher.upgrade(Zero::zero());
assert_panic_with_felt_error(result, UpgradeableErrors::INVALID_CLASS);
}

#[test]
fn test_swap_to_new() {
let cfg = generic_test_fixture();
let amount = INITIAL_CONTRACT_SUPPLY / 10;
let user = new_user(:cfg, id: 0, legacy_supply: amount);
let usdc_migration_contract = cfg.usdc_migration_contract;
let usdc_migration_dispatcher = IUSDCMigrationDispatcher {
contract_address: usdc_migration_contract,
};
let legacy_token_address = cfg.legacy_token.contract_address();
let new_token_address = cfg.new_token.contract_address();
let legacy_dispatcher = IERC20Dispatcher { contract_address: legacy_token_address };
let new_dispatcher = IERC20Dispatcher { contract_address: new_token_address };

// Spy events.
let mut spy = spy_events();

// Approve and migrate.
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
legacy_dispatcher.approve(spender: usdc_migration_contract, :amount);
cheat_caller_address_once(contract_address: usdc_migration_contract, caller_address: user);
usdc_migration_dispatcher.swap_to_new(:amount);

// Assert user balances are correct.
assert_eq!(legacy_dispatcher.balance_of(account: user), 0);
assert_eq!(new_dispatcher.balance_of(account: user), amount);

// Assert contract balances are correct.
assert_eq!(legacy_dispatcher.balance_of(account: usdc_migration_contract), amount);
assert_eq!(
new_dispatcher.balance_of(account: usdc_migration_contract),
INITIAL_CONTRACT_SUPPLY - amount,
);

// Assert event is emitted.
let events = spy.get_events().emitted_by(contract_address: usdc_migration_contract).events;
assert_number_of_events(actual: events.len(), expected: 1, message: "migrate");
assert_expected_event_emitted(
spied_event: events[0],
expected_event: USDCMigrated {
user, from_token: legacy_token_address, to_token: new_token_address, amount,
},
expected_event_selector: @selector!("USDCMigrated"),
expected_event_name: "USDCMigrated",
);
}

#[test]
#[feature("safe_dispatcher")]
fn test_swap_to_new_assertions() {
let cfg = deploy_usdc_migration();
let amount = INITIAL_SUPPLY / 10;
let user = new_user(:cfg, id: 0, legacy_supply: 0);
let usdc_migration_contract = cfg.usdc_migration_contract;
let usdc_migration_safe_dispatcher = IUSDCMigrationSafeDispatcher {
contract_address: usdc_migration_contract,
};
let legacy_token_address = cfg.legacy_token.contract_address();
let legacy_dispatcher = IERC20Dispatcher { contract_address: legacy_token_address };

// Insufficient user balance.
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
legacy_dispatcher.approve(spender: usdc_migration_contract, :amount);
cheat_caller_address_once(contract_address: cfg.usdc_migration_contract, caller_address: user);
let res = usdc_migration_safe_dispatcher.swap_to_new(:amount);
assert_panic_with_error(res, Erc20Error::INSUFFICIENT_BALANCE.describe());

// Insufficient allowance.
supply_contract(target: user, token: cfg.legacy_token, :amount);
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
legacy_dispatcher.approve(spender: usdc_migration_contract, amount: amount / 2);
cheat_caller_address_once(contract_address: usdc_migration_contract, caller_address: user);
let res = usdc_migration_safe_dispatcher.swap_to_new(:amount);
assert_panic_with_error(res, Erc20Error::INSUFFICIENT_ALLOWANCE.describe());

// Insufficient contract balance.
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
legacy_dispatcher.approve(spender: usdc_migration_contract, :amount);
cheat_caller_address_once(contract_address: cfg.usdc_migration_contract, caller_address: user);
let res = usdc_migration_safe_dispatcher.swap_to_new(:amount);
assert_panic_with_error(res, Erc20Error::INSUFFICIENT_BALANCE.describe());
}
35 changes: 33 additions & 2 deletions packages/usdc_migration/src/tests/test_utils.cairo
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
use constants::{INITIAL_SUPPLY, L1_RECIPIENT, LEGACY_THRESHOLD, OWNER_ADDRESS, STARKGATE_ADDRESS};
use snforge_std::{ContractClassTrait, CustomToken, DeclareResultTrait, Token, TokenTrait};
use constants::{
INITIAL_CONTRACT_SUPPLY, INITIAL_SUPPLY, L1_RECIPIENT, LEGACY_THRESHOLD, OWNER_ADDRESS,
STARKGATE_ADDRESS,
};
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use snforge_std::{
ContractClassTrait, CustomToken, DeclareResultTrait, Token, TokenTrait, set_balance,
};
use starknet::{ContractAddress, EthAddress};
use starkware_utils_testing::test_utils::{Deployable, TokenConfig};

Expand All @@ -23,6 +29,7 @@ pub(crate) mod constants {
* 10_u256.pow(6); // 140 * million * decimals
// TODO: Change to the real value.
pub const LEGACY_THRESHOLD: u256 = 100_000;
pub const INITIAL_CONTRACT_SUPPLY: u256 = INITIAL_SUPPLY / 20;
pub fn OWNER_ADDRESS() -> ContractAddress {
'OWNER_ADDRESS'.try_into().unwrap()
}
Expand All @@ -34,6 +41,14 @@ pub(crate) mod constants {
}
}

pub(crate) fn generic_test_fixture() -> USDCMigrationCfg {
let cfg = deploy_usdc_migration();
supply_contract(
target: cfg.usdc_migration_contract, token: cfg.new_token, amount: INITIAL_CONTRACT_SUPPLY,
);
cfg
}

fn deploy_tokens() -> (Token, Token) {
let legacy_config = TokenConfig {
name: "Legacy-USDC",
Expand Down Expand Up @@ -86,6 +101,22 @@ pub(crate) fn deploy_usdc_migration() -> USDCMigrationCfg {
}
}

pub(crate) fn new_user(cfg: USDCMigrationCfg, id: u8, legacy_supply: u256) -> ContractAddress {
let user_address = _generate_user_address(:id);
set_balance(target: user_address, new_balance: legacy_supply, token: cfg.legacy_token);
user_address
}

fn _generate_user_address(id: u8) -> ContractAddress {
('USER_ADDRESS' + id.into()).try_into().unwrap()
}

pub(crate) fn supply_contract(target: ContractAddress, token: Token, amount: u256) {
let current_balance = IERC20Dispatcher { contract_address: token.contract_address() }
.balance_of(account: target);
set_balance(:target, new_balance: current_balance + amount, :token);
}

// TODO: Move to starkware_utils_testing.
pub(crate) fn load_contract_address(
target: ContractAddress, storage_address: felt252,
Expand Down
43 changes: 41 additions & 2 deletions packages/usdc_migration/src/usdc_migration.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,13 @@ pub mod USDCMigration {
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
use openzeppelin::upgrades::interface::IUpgradeable;
use openzeppelin::upgrades::upgradeable::UpgradeableComponent;
use starknet::storage::StoragePointerWriteAccess;
use starknet::{ClassHash, ContractAddress, EthAddress};
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
use starknet::{
ClassHash, ContractAddress, EthAddress, get_caller_address, get_contract_address,
};
use starkware_utils::constants::MAX_U256;
use starkware_utils::erc20::erc20_utils::CheckedIERC20DispatcherTrait;
use usdc_migration::events::USDCMigrationEvents::USDCMigrated;
use usdc_migration::interface::{IUSDCMigration, IUSDCMigrationConfig};

component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
Expand Down Expand Up @@ -37,6 +41,7 @@ pub mod USDCMigration {
pub enum Event {
OwnableEvent: OwnableComponent::Event,
UpgradeableEvent: UpgradeableComponent::Event,
USDCMigrated: USDCMigrated,
}

#[constructor]
Expand Down Expand Up @@ -77,6 +82,15 @@ pub mod USDCMigration {

#[abi(embed_v0)]
pub impl USDCMigrationImpl of IUSDCMigration<ContractState> { //impl logic
fn swap_to_new(ref self: ContractState, amount: u256) {
self
._swap(
from_token: self.legacy_token_dispatcher.read(),
to_token: self.new_token_dispatcher.read(),
:amount,
);
// TODO: send to l1 if threshold is reached.
}
}

#[abi(embed_v0)]
Expand All @@ -91,4 +105,29 @@ pub mod USDCMigration {
// TODO: Send to L1 here according the new threshold?
}
}

#[generate_trait]
impl USDCMigrationInternalImpl of USDCMigrationInternalTrait {
fn _swap(
ref self: ContractState,
from_token: IERC20Dispatcher,
to_token: IERC20Dispatcher,
amount: u256,
) {
let contract_address = get_contract_address();
let user = get_caller_address();
from_token.checked_transfer_from(sender: user, recipient: contract_address, :amount);
to_token.checked_transfer(recipient: user, :amount);

self
.emit(
USDCMigrated {
user,
from_token: from_token.contract_address,
to_token: to_token.contract_address,
amount,
},
);
}
}
}