Skip to content

Commit eff588c

Browse files
feat: migrate function implementation
1 parent 1b1b495 commit eff588c

File tree

7 files changed

+190
-14
lines changed

7 files changed

+190
-14
lines changed

Scarb.lock

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -146,15 +146,15 @@ dependencies = [
146146
[[package]]
147147
name = "starkware_utils"
148148
version = "1.0.0"
149-
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=e11de236fe1e36c9556690749f834b6d0e1c4d49#e11de236fe1e36c9556690749f834b6d0e1c4d49"
149+
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=123332b0895ce9116fb2eaa2469739250c3f733d#123332b0895ce9116fb2eaa2469739250c3f733d"
150150
dependencies = [
151151
"openzeppelin",
152152
]
153153

154154
[[package]]
155155
name = "starkware_utils_testing"
156156
version = "1.0.0"
157-
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=e11de236fe1e36c9556690749f834b6d0e1c4d49#e11de236fe1e36c9556690749f834b6d0e1c4d49"
157+
source = "git+https://github.com/starkware-libs/starkware-starknet-utils?rev=123332b0895ce9116fb2eaa2469739250c3f733d#123332b0895ce9116fb2eaa2469739250c3f733d"
158158
dependencies = [
159159
"openzeppelin",
160160
"snforge_std",

Scarb.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ snforge_std = "0.49.0"
1515
assert_macros = "2.12.2"
1616
openzeppelin = "2.0.0"
1717
openzeppelin_testing = "4.2.0"
18-
starkware_utils = { git ="https://github.com/starkware-libs/starkware-starknet-utils", rev="e11de236fe1e36c9556690749f834b6d0e1c4d49" }
19-
starkware_utils_testing = { git="https://github.com/starkware-libs/starkware-starknet-utils", rev="e11de236fe1e36c9556690749f834b6d0e1c4d49" }
18+
starkware_utils = { git ="https://github.com/starkware-libs/starkware-starknet-utils", rev="123332b0895ce9116fb2eaa2469739250c3f733d" }
19+
starkware_utils_testing = { git="https://github.com/starkware-libs/starkware-starknet-utils", rev="123332b0895ce9116fb2eaa2469739250c3f733d" }
2020

2121
[workspace.tool.fmt]
2222
sort-module-level-items = true
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,14 @@
1+
pub mod USDCMigrationEvents {
2+
use starknet::ContractAddress;
13

4+
#[derive(Drop, starknet::Event, Debug, PartialEq)]
5+
pub struct USDCMigrated {
6+
#[key]
7+
pub user: ContractAddress,
8+
#[key]
9+
pub from_token: ContractAddress,
10+
#[key]
11+
pub to_token: ContractAddress,
12+
pub amount: u256,
13+
}
14+
}

packages/usdc_migration/src/interface.cairo

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
#[starknet::interface]
2-
pub trait IUSDCMigration<T> { //interface
2+
pub trait IUSDCMigration<T> {
3+
/// Exchanges (1:1) `amount` of legacy token for new token.
4+
/// Precondition: Sufficient allowance of legacy token.
5+
fn swap_to_new(ref self: T, amount: u256);
36
}
47

58
#[starknet::interface]

packages/usdc_migration/src/tests/test_usdc_migration.cairo

Lines changed: 92 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -7,16 +7,28 @@ use openzeppelin::upgrades::interface::{
77
IUpgradeableSafeDispatcherTrait,
88
};
99
use openzeppelin::upgrades::upgradeable::UpgradeableComponent::Errors as UpgradeableErrors;
10-
use snforge_std::{DeclareResultTrait, TokenTrait};
10+
use snforge_std::{DeclareResultTrait, EventSpyTrait, EventsFilterTrait, TokenTrait, spy_events};
1111
use starkware_utils::constants::MAX_U256;
12-
use starkware_utils_testing::test_utils::{assert_panic_with_felt_error, cheat_caller_address_once};
12+
use starkware_utils::erc20::erc20_errors::Erc20Error;
13+
use starkware_utils::errors::Describable;
14+
use starkware_utils_testing::event_test_utils::assert_number_of_events;
15+
use starkware_utils_testing::test_utils::{
16+
assert_expected_event_emitted, assert_panic_with_error, assert_panic_with_felt_error,
17+
cheat_caller_address_once,
18+
};
19+
use usdc_migration::events::USDCMigrationEvents::USDCMigrated;
1320
use usdc_migration::interface::{
1421
IUSDCMigrationConfigDispatcher, IUSDCMigrationConfigDispatcherTrait,
1522
IUSDCMigrationConfigSafeDispatcher, IUSDCMigrationConfigSafeDispatcherTrait,
23+
IUSDCMigrationDispatcher, IUSDCMigrationDispatcherTrait, IUSDCMigrationSafeDispatcher,
24+
IUSDCMigrationSafeDispatcherTrait,
25+
};
26+
use usdc_migration::tests::test_utils::constants::{
27+
INITIAL_CONTRACT_SUPPLY, INITIAL_SUPPLY, LEGACY_THRESHOLD,
28+
};
29+
use usdc_migration::tests::test_utils::{
30+
deploy_usdc_migration, generic_test_fixture, load_contract_address, load_u256, new_user,
1631
};
17-
use usdc_migration::tests::test_utils::constants::LEGACY_THRESHOLD;
18-
use usdc_migration::tests::test_utils::{deploy_usdc_migration, load_contract_address, load_u256};
19-
2032
#[test]
2133
fn test_constructor() {
2234
let cfg = deploy_usdc_migration();
@@ -116,3 +128,78 @@ fn test_upgrade_assertions() {
116128
let result = upgradeable_safe_dispatcher.upgrade(Zero::zero());
117129
assert_panic_with_felt_error(result, UpgradeableErrors::INVALID_CLASS);
118130
}
131+
132+
#[test]
133+
fn test_swap_to_new() {
134+
let cfg = generic_test_fixture();
135+
let amount = INITIAL_CONTRACT_SUPPLY / 10;
136+
let user = new_user(:cfg, id: 0, legacy_supply: amount);
137+
let usdc_migration_contract = cfg.usdc_migration_contract;
138+
let usdc_migration_dispatcher = IUSDCMigrationDispatcher {
139+
contract_address: usdc_migration_contract,
140+
};
141+
let legacy_token_address = cfg.legacy_token.contract_address();
142+
let new_token_address = cfg.new_token.contract_address();
143+
let legacy_dispatcher = IERC20Dispatcher { contract_address: legacy_token_address };
144+
let new_dispatcher = IERC20Dispatcher { contract_address: new_token_address };
145+
146+
// Spy events.
147+
let mut spy = spy_events();
148+
149+
// Approve and migrate.
150+
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
151+
legacy_dispatcher.approve(spender: usdc_migration_contract, :amount);
152+
cheat_caller_address_once(contract_address: usdc_migration_contract, caller_address: user);
153+
usdc_migration_dispatcher.swap_to_new(:amount);
154+
155+
// Assert user balances are correct.
156+
assert_eq!(legacy_dispatcher.balance_of(account: user), 0);
157+
assert_eq!(new_dispatcher.balance_of(account: user), amount);
158+
159+
// Assert contract balances are correct.
160+
assert_eq!(legacy_dispatcher.balance_of(account: usdc_migration_contract), amount);
161+
assert_eq!(
162+
new_dispatcher.balance_of(account: usdc_migration_contract),
163+
INITIAL_CONTRACT_SUPPLY - amount,
164+
);
165+
166+
// Assert event is emitted.
167+
let events = spy.get_events().emitted_by(contract_address: usdc_migration_contract).events;
168+
assert_number_of_events(actual: events.len(), expected: 1, message: "migrate");
169+
assert_expected_event_emitted(
170+
spied_event: events[0],
171+
expected_event: USDCMigrated {
172+
user, from_token: legacy_token_address, to_token: new_token_address, amount,
173+
},
174+
expected_event_selector: @selector!("USDCMigrated"),
175+
expected_event_name: "USDCMigrated",
176+
);
177+
}
178+
179+
#[test]
180+
#[feature("safe_dispatcher")]
181+
fn test_swap_to_new_assertions() {
182+
let cfg = deploy_usdc_migration();
183+
let amount = INITIAL_SUPPLY / 10;
184+
let user = new_user(:cfg, id: 0, legacy_supply: amount);
185+
let usdc_migration_contract = cfg.usdc_migration_contract;
186+
let usdc_migration_safe_dispatcher = IUSDCMigrationSafeDispatcher {
187+
contract_address: usdc_migration_contract,
188+
};
189+
let legacy_token_address = cfg.legacy_token.contract_address();
190+
let legacy_dispatcher = IERC20Dispatcher { contract_address: legacy_token_address };
191+
192+
// Insufficient allowance.
193+
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
194+
legacy_dispatcher.approve(spender: usdc_migration_contract, amount: amount / 2);
195+
cheat_caller_address_once(contract_address: usdc_migration_contract, caller_address: user);
196+
let res = usdc_migration_safe_dispatcher.swap_to_new(:amount);
197+
assert_panic_with_error(res, Erc20Error::INSUFFICIENT_ALLOWANCE.describe());
198+
199+
// Insufficient balance.
200+
cheat_caller_address_once(contract_address: legacy_token_address, caller_address: user);
201+
legacy_dispatcher.approve(spender: usdc_migration_contract, :amount);
202+
cheat_caller_address_once(contract_address: cfg.usdc_migration_contract, caller_address: user);
203+
let res = usdc_migration_safe_dispatcher.swap_to_new(:amount);
204+
assert_panic_with_error(res, Erc20Error::INSUFFICIENT_BALANCE.describe());
205+
}

packages/usdc_migration/src/tests/test_utils.cairo

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
1-
use constants::{INITIAL_SUPPLY, L1_RECIPIENT, LEGACY_THRESHOLD, OWNER_ADDRESS, STARKGATE_ADDRESS};
2-
use snforge_std::{ContractClassTrait, CustomToken, DeclareResultTrait, Token, TokenTrait};
1+
use constants::{
2+
INITIAL_CONTRACT_SUPPLY, INITIAL_SUPPLY, L1_RECIPIENT, LEGACY_THRESHOLD, OWNER_ADDRESS,
3+
STARKGATE_ADDRESS,
4+
};
5+
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
6+
use snforge_std::{
7+
ContractClassTrait, CustomToken, DeclareResultTrait, Token, TokenTrait, set_balance,
8+
};
39
use starknet::{ContractAddress, EthAddress};
410
use starkware_utils_testing::test_utils::{Deployable, TokenConfig};
511

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

44+
pub(crate) fn generic_test_fixture() -> USDCMigrationCfg {
45+
let cfg = deploy_usdc_migration();
46+
supply_contract(
47+
target: cfg.usdc_migration_contract, token: cfg.new_token, amount: INITIAL_CONTRACT_SUPPLY,
48+
);
49+
cfg
50+
}
51+
3752
fn deploy_tokens() -> (Token, Token) {
3853
let legacy_config = TokenConfig {
3954
name: "Legacy-USDC",
@@ -86,6 +101,22 @@ pub(crate) fn deploy_usdc_migration() -> USDCMigrationCfg {
86101
}
87102
}
88103

104+
pub(crate) fn new_user(cfg: USDCMigrationCfg, id: u8, legacy_supply: u256) -> ContractAddress {
105+
let user_address = _generate_user_address(:id);
106+
set_balance(target: user_address, new_balance: legacy_supply, token: cfg.legacy_token);
107+
user_address
108+
}
109+
110+
fn _generate_user_address(id: u8) -> ContractAddress {
111+
('USER_ADDRESS' + id.into()).try_into().unwrap()
112+
}
113+
114+
pub(crate) fn supply_contract(target: ContractAddress, token: Token, amount: u256) {
115+
let current_balance = IERC20Dispatcher { contract_address: token.contract_address() }
116+
.balance_of(account: target);
117+
set_balance(:target, new_balance: current_balance + amount, :token);
118+
}
119+
89120
// TODO: Move to starkware_utils_testing.
90121
pub(crate) fn load_contract_address(
91122
target: ContractAddress, storage_address: felt252,

packages/usdc_migration/src/usdc_migration.cairo

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,13 @@ pub mod USDCMigration {
44
use openzeppelin::token::erc20::interface::{IERC20Dispatcher, IERC20DispatcherTrait};
55
use openzeppelin::upgrades::interface::IUpgradeable;
66
use openzeppelin::upgrades::upgradeable::UpgradeableComponent;
7-
use starknet::storage::StoragePointerWriteAccess;
8-
use starknet::{ClassHash, ContractAddress, EthAddress};
7+
use starknet::storage::{StoragePointerReadAccess, StoragePointerWriteAccess};
8+
use starknet::{
9+
ClassHash, ContractAddress, EthAddress, get_caller_address, get_contract_address,
10+
};
911
use starkware_utils::constants::MAX_U256;
12+
use starkware_utils::erc20::erc20_utils::CheckedIERC20DispatcherTrait;
13+
use usdc_migration::events::USDCMigrationEvents::USDCMigrated;
1014
use usdc_migration::interface::{IUSDCMigration, IUSDCMigrationConfig};
1115

1216
component!(path: OwnableComponent, storage: ownable, event: OwnableEvent);
@@ -37,6 +41,7 @@ pub mod USDCMigration {
3741
pub enum Event {
3842
OwnableEvent: OwnableComponent::Event,
3943
UpgradeableEvent: UpgradeableComponent::Event,
44+
USDCMigrated: USDCMigrated,
4045
}
4146

4247
#[constructor]
@@ -77,6 +82,15 @@ pub mod USDCMigration {
7782

7883
#[abi(embed_v0)]
7984
pub impl USDCMigrationImpl of IUSDCMigration<ContractState> { //impl logic
85+
fn swap_to_new(ref self: ContractState, amount: u256) {
86+
self
87+
._swap(
88+
from_token: self.legacy_token_dispatcher.read(),
89+
to_token: self.new_token_dispatcher.read(),
90+
:amount,
91+
);
92+
// TODO: send to l1 if threshold is reached.
93+
}
8094
}
8195

8296
#[abi(embed_v0)]
@@ -91,4 +105,32 @@ pub mod USDCMigration {
91105
// TODO: Send to L1 here according the new threshold?
92106
}
93107
}
108+
109+
#[generate_trait]
110+
impl USDCMigrationInternalImpl of USDCMigrationInternalTrait {
111+
fn _swap(
112+
ref self: ContractState,
113+
from_token: IERC20Dispatcher,
114+
to_token: IERC20Dispatcher,
115+
amount: u256,
116+
) {
117+
let contract_address = get_contract_address();
118+
let caller_address = get_caller_address();
119+
from_token
120+
.checked_transfer_from(
121+
sender: caller_address, recipient: contract_address, :amount,
122+
);
123+
to_token.checked_transfer(recipient: caller_address, :amount);
124+
125+
self
126+
.emit(
127+
USDCMigrated {
128+
user: caller_address,
129+
from_token: from_token.contract_address,
130+
to_token: to_token.contract_address,
131+
amount,
132+
},
133+
);
134+
}
135+
}
94136
}

0 commit comments

Comments
 (0)