Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Migrate upgrades #603

Merged
merged 65 commits into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
65 commits
Select commit Hold shift + click to select a range
9741209
add upgrades to lib
andrew-fleming Apr 3, 2023
042085c
add proxy mod
andrew-fleming Apr 3, 2023
08e33b6
update cairo
andrew-fleming Apr 6, 2023
61960c3
update Cargo
andrew-fleming Apr 6, 2023
2f3e291
fix test command
andrew-fleming Apr 6, 2023
e7ecd30
add upgrades
andrew-fleming Apr 6, 2023
4f0f426
fix spelling
andrew-fleming Apr 6, 2023
9db3cf5
add upgrade test
andrew-fleming Apr 7, 2023
9151274
fix spelling
andrew-fleming Apr 7, 2023
328769d
add upgrade_and_call
andrew-fleming Apr 7, 2023
fe652e7
update cairo
andrew-fleming Apr 9, 2023
2692c96
update Cargo
andrew-fleming Apr 9, 2023
c6e4499
add events
andrew-fleming Apr 9, 2023
c8bf693
tidy up code
andrew-fleming Apr 9, 2023
e06256a
fix format
andrew-fleming Apr 10, 2023
eca5b3d
update cairo to alpha7
andrew-fleming Apr 28, 2023
d73de7f
update Cargo
andrew-fleming Apr 28, 2023
dda8e47
fix expected syntax
andrew-fleming Apr 28, 2023
4fcd5f3
Merge branch 'cairo-1' into cairo1-upgrades
andrew-fleming Apr 28, 2023
831575b
fix conflict
andrew-fleming Apr 28, 2023
3324dd2
update expected syntax
andrew-fleming Apr 28, 2023
c54582f
formatting
andrew-fleming Apr 28, 2023
f7a009d
update cairo to rc0
andrew-fleming May 6, 2023
31d0663
update Cargo
andrew-fleming May 6, 2023
09558eb
fix import
andrew-fleming May 6, 2023
8419b6c
refactor mocks
andrew-fleming May 6, 2023
731f9ef
start test refactor
andrew-fleming May 6, 2023
af785c8
simplify upgrades, add upgrade_and_call
andrew-fleming May 6, 2023
9b6fce3
refactor mocks
andrew-fleming May 8, 2023
ad665b5
refactor upgrades
andrew-fleming May 8, 2023
4125a85
refactor tests
andrew-fleming May 8, 2023
c43fa0a
fix imports, add impl
andrew-fleming May 12, 2023
b867988
fix imports
andrew-fleming May 12, 2023
a07aeed
update branch
andrew-fleming May 14, 2023
b1b76f9
fix formatting
andrew-fleming May 14, 2023
2abf6f3
Merge branch 'cairo-1' into cairo1-upgrades
andrew-fleming May 20, 2023
72c9903
add abis to mocks
andrew-fleming May 21, 2023
baf4be3
refactor tests with dispatchers
andrew-fleming May 21, 2023
a85c5ae
fix formatting
andrew-fleming May 21, 2023
1feb1c0
fix conflicts
andrew-fleming Jul 21, 2023
dc6216e
start migration
andrew-fleming Jul 22, 2023
9cc872b
fix conflicts
andrew-fleming Jul 23, 2023
6e1d671
update syntax
andrew-fleming Jul 23, 2023
7e5be6a
update syntax in mocks
andrew-fleming Jul 23, 2023
0be0050
fix tests
andrew-fleming Jul 23, 2023
863e9b3
add line
andrew-fleming Jul 24, 2023
1dc71fe
remove line
andrew-fleming Jul 24, 2023
897d8c8
add upgradeable trait
andrew-fleming Jul 25, 2023
fb30b8d
replace interface impl with trait impl
andrew-fleming Jul 25, 2023
c408281
fix formatting
andrew-fleming Jul 25, 2023
fb81b9a
add assertion
andrew-fleming Jul 25, 2023
547e1d4
add tests for events
andrew-fleming Jul 27, 2023
3e45d7f
Apply suggestions from code review
andrew-fleming Jul 27, 2023
bf28bf1
move logic to internal impl
andrew-fleming Jul 27, 2023
d7754a6
add interface
andrew-fleming Jul 27, 2023
50e063e
update paths
andrew-fleming Jul 27, 2023
98e65d3
fix formatting
andrew-fleming Jul 27, 2023
07e3069
simplify imports
andrew-fleming Jul 27, 2023
e68e548
Apply suggestions from code review
andrew-fleming Jul 27, 2023
2dcd61c
Apply suggestions from code review
andrew-fleming Aug 4, 2023
af818b7
remove upgrade_and_call, test event in separate test
andrew-fleming Aug 4, 2023
421687e
remove upgrade_and_call
andrew-fleming Aug 4, 2023
53e4f00
remove unused import
andrew-fleming Aug 4, 2023
01e90f6
reorder imports, tidy up code
andrew-fleming Aug 4, 2023
5b54627
fix conflicts
andrew-fleming Aug 4, 2023
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
1 change: 1 addition & 0 deletions src/openzeppelin/lib.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ mod introspection;
mod security;
mod tests;
mod token;
mod upgrades;
mod utils;
1 change: 1 addition & 0 deletions src/openzeppelin/tests.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,5 @@ mod introspection;
mod mocks;
mod security;
mod token;
mod upgrades;
mod utils;
2 changes: 2 additions & 0 deletions src/openzeppelin/tests/mocks.cairo
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,5 @@ mod camel721_mock;
mod non_implementing_mock;
mod dual721_receiver_mocks;
mod src5_mocks;
mod upgrades_v1;
mod upgrades_v2;
70 changes: 70 additions & 0 deletions src/openzeppelin/tests/mocks/upgrades_v1.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// This contract is a mock used to test the core functionality of the upgrade functions.
// The functions are NOT PROTECTED.
// DO NOT USE IN PRODUCTION.

use array::ArrayTrait;
use starknet::class_hash::ClassHash;
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

#[starknet::interface]
trait IUpgradesV1<TState> {
fn upgrade(ref self: TState, impl_hash: ClassHash);
fn upgrade_and_call(
ref self: TState, impl_hash: ClassHash, selector: felt252, calldata: Span<felt252>
);
fn set_value(ref self: TState, val: felt252);
fn get_value(self: @TState) -> felt252;
fn remove_selector(self: @TState);
}

trait UpgradesV1Trait<TState> {
fn set_value(ref self: TState, val: felt252);
fn get_value(self: @TState) -> felt252;
fn remove_selector(self: @TState);
}

#[starknet::contract]
mod UpgradesV1 {
use array::ArrayTrait;
use starknet::class_hash::ClassHash;
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
use starknet::ContractAddress;
use openzeppelin::upgrades::upgradeable::Upgradeable;
use openzeppelin::upgrades::upgradeable::UpgradeableTrait;

#[storage]
struct Storage {
value: felt252
}

#[external(v0)]
impl UpgradeableImpl of UpgradeableTrait<ContractState> {
fn upgrade(ref self: ContractState, impl_hash: ClassHash) {
let mut unsafe_state = Upgradeable::unsafe_new_contract_state();
Upgradeable::UpgradeableImpl::upgrade(ref unsafe_state, impl_hash);
}

fn upgrade_and_call(
ref self: ContractState,
impl_hash: ClassHash,
selector: felt252,
calldata: Span<felt252>
) {
let mut unsafe_state = Upgradeable::unsafe_new_contract_state();
Upgradeable::UpgradeableImpl::upgrade_and_call(
ref unsafe_state, impl_hash, selector, calldata
);
}
}

#[external(v0)]
impl UpgradesV1Impl of super::UpgradesV1Trait<ContractState> {
fn set_value(ref self: ContractState, val: felt252) {
self.value.write(val);
}

fn get_value(self: @ContractState) -> felt252 {
self.value.read()
}

fn remove_selector(self: @ContractState) {}
}
}
79 changes: 79 additions & 0 deletions src/openzeppelin/tests/mocks/upgrades_v2.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
// This contract is a mock used to test the core functionality of the upgrade functions.
// The functions are NOT PROTECTED.
// DO NOT USE IN PRODUCTION.

use array::ArrayTrait;
use starknet::class_hash::ClassHash;
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

#[starknet::interface]
trait IUpgradesV2<TState> {
fn upgrade(ref self: TState, impl_hash: ClassHash);
fn upgrade_and_call(
ref self: TState, impl_hash: ClassHash, selector: felt252, calldata: Span<felt252>
);
fn set_value(ref self: TState, val: felt252);
fn set_value2(ref self: TState, val: felt252);
fn get_value(self: @TState) -> felt252;
fn get_value2(self: @TState) -> felt252;
}

trait UpgradesV2Trait<TState> {
fn set_value(ref self: TState, val: felt252);
fn set_value2(ref self: TState, val: felt252);
fn get_value(self: @TState) -> felt252;
fn get_value2(self: @TState) -> felt252;
}

#[starknet::contract]
mod UpgradesV2 {
use array::ArrayTrait;
use starknet::class_hash::ClassHash;
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
use starknet::ContractAddress;
use openzeppelin::upgrades::upgradeable::Upgradeable;
use openzeppelin::upgrades::upgradeable::UpgradeableTrait;

#[storage]
struct Storage {
value: felt252,
value2: felt252,
}

#[external(v0)]
impl UpgradeableImpl of UpgradeableTrait<ContractState> {
fn upgrade(ref self: ContractState, impl_hash: ClassHash) {
let mut unsafe_state = Upgradeable::unsafe_new_contract_state();
Upgradeable::UpgradeableImpl::upgrade(ref unsafe_state, impl_hash);
}

fn upgrade_and_call(
ref self: ContractState,
impl_hash: ClassHash,
selector: felt252,
calldata: Span<felt252>
) {
let mut unsafe_state = Upgradeable::unsafe_new_contract_state();
Upgradeable::UpgradeableImpl::upgrade_and_call(
ref unsafe_state, impl_hash, selector, calldata
);
}
}

#[external(v0)]
impl UpgradesV2Impl of super::UpgradesV2Trait<ContractState> {
fn set_value(ref self: ContractState, val: felt252) {
self.value.write(val);
}

fn set_value2(ref self: ContractState, val: felt252) {
self.value2.write(val);
}

fn get_value(self: @ContractState) -> felt252 {
self.value.read()
}

fn get_value2(self: @ContractState) -> felt252 {
self.value2.read()
}
}
}
1 change: 1 addition & 0 deletions src/openzeppelin/tests/upgrades.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod test_upgradeable;
175 changes: 175 additions & 0 deletions src/openzeppelin/tests/upgrades/test_upgradeable.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1,175 @@
use array::ArrayTrait;
use option::OptionTrait;
use starknet::class_hash::ClassHash;
use starknet::class_hash::class_hash_const;
use starknet::class_hash::Felt252TryIntoClassHash;
use starknet::ContractAddress;
use starknet::contract_address_const;
use starknet::testing;
use traits::TryInto;

use openzeppelin::tests::mocks::upgrades_v1::UpgradesV1;
use openzeppelin::tests::mocks::upgrades_v1::IUpgradesV1Dispatcher;
use openzeppelin::tests::mocks::upgrades_v1::IUpgradesV1DispatcherTrait;
use openzeppelin::tests::mocks::upgrades_v2::UpgradesV2;
use openzeppelin::tests::mocks::upgrades_v2::IUpgradesV2Dispatcher;
use openzeppelin::tests::mocks::upgrades_v2::IUpgradesV2DispatcherTrait;
use openzeppelin::upgrades::upgradeable::Upgradeable;
use openzeppelin::upgrades::upgradeable::Upgradeable::Upgraded;
use openzeppelin::tests::utils;

const VALUE: felt252 = 123;
const VALUE2: felt252 = 789;
const SET_VALUE_SELECTOR: felt252 =
0x3d7905601c217734671143d457f0db37f7f8883112abd34b92c4abfeafde0c3;
const SET_VALUE2_SELECTOR: felt252 =
0x47c8c185eb97d3925831a1c97e43bd9077181d2b200133ede551f1c47056a3;
const REMOVE_SELECTOR: felt252 = 0x2beeaa48bce210364c7d2f2fbb677e08136cb29e8972a6728249364dde19e6f;

fn V2_CLASS_HASH() -> ClassHash {
UpgradesV2::TEST_CLASS_HASH.try_into().unwrap()
}

fn CLASS_HASH_ZERO() -> ClassHash {
class_hash_const::<0>()
}

fn ZERO() -> ContractAddress {
contract_address_const::<0>()
}

//
// Setup
//

fn deploy_v1() -> IUpgradesV1Dispatcher {
let calldata = array![];
let address = utils::deploy(UpgradesV1::TEST_CLASS_HASH, calldata);
IUpgradesV1Dispatcher { contract_address: address }
}

//
// upgrade
//

#[test]
#[available_gas(2000000)]
#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED', ))]
fn test_upgrade_with_class_hash_zero() {
let v1 = deploy_v1();
v1.upgrade(CLASS_HASH_ZERO());
}

#[test]
#[available_gas(2000000)]
fn test_persisting_selector_after_upgrade() {
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
let v1 = deploy_v1();
v1.set_value(VALUE);
assert(v1.get_value() == VALUE, 'Should be VALUE');

// Upgrade and check event
testing::pop_log_raw(ZERO());
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved
v1.upgrade(V2_CLASS_HASH());
let event = testing::pop_log::<Upgraded>(v1.contract_address).unwrap();
assert(event.implementation == V2_CLASS_HASH(), 'Invalid implementation');

let v2 = IUpgradesV2Dispatcher { contract_address: v1.contract_address };
v2.set_value(VALUE2);
assert(v2.get_value() == VALUE2, 'Selector should be callable');
}

#[test]
#[available_gas(2000000)]
fn test_new_selector_after_upgrade() {
let v1 = deploy_v1();

v1.upgrade(V2_CLASS_HASH());
let v2 = IUpgradesV2Dispatcher { contract_address: v1.contract_address };

v2.set_value2(VALUE);
assert(v2.get_value2() == VALUE, 'New selector should be callable');
}

#[test]
#[available_gas(2000000)]
fn test_state_persists_after_upgrade() {
let v1 = deploy_v1();
v1.set_value(VALUE);

v1.upgrade(V2_CLASS_HASH());
let v2 = IUpgradesV2Dispatcher { contract_address: v1.contract_address };

assert(v2.get_value() == VALUE, 'Should keep state after upgrade');
}

#[test]
#[available_gas(2000000)]
fn test_remove_selector_passes_in_v1() {
let v1 = deploy_v1();
v1.remove_selector();
}

#[test]
#[available_gas(2000000)]
#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND', ))]
fn test_remove_selector_fails_in_v2() {
let v1 = deploy_v1();
v1.upgrade(V2_CLASS_HASH());
// We use the v1 dispatcher because remove_selector is not in v2 interface
v1.remove_selector();
}

//
// upgrade_and_call
//

#[test]
#[available_gas(2000000)]
#[should_panic(expected: ('Class hash cannot be zero', 'ENTRYPOINT_FAILED', ))]
fn test_upgrade_and_call_with_class_hash_zero() {
let v1 = deploy_v1();
let calldata = array![VALUE];
v1.upgrade_and_call(CLASS_HASH_ZERO(), SET_VALUE_SELECTOR, calldata.span());
}

#[test]
#[available_gas(2000000)]
fn test_upgrade_and_call_persisting_selector() {
let v1 = deploy_v1();
assert(v1.get_value() == 0, 'Should be zero');

testing::pop_log_raw(ZERO());
andrew-fleming marked this conversation as resolved.
Show resolved Hide resolved

// upgrade_and_call and check event
let calldata = array![VALUE];
v1.upgrade_and_call(V2_CLASS_HASH(), SET_VALUE_SELECTOR, calldata.span());
let event = testing::pop_log::<Upgraded>(v1.contract_address).unwrap();
assert(event.implementation == V2_CLASS_HASH(), 'Invalid implementation');

// Check value is set
let v2 = IUpgradesV2Dispatcher { contract_address: v1.contract_address };
assert(v2.get_value() == VALUE, 'Should set val during upgrade');
}

#[test]
#[available_gas(2000000)]
fn test_upgrade_and_call_new_selector() {
let v1 = deploy_v1();

// upgrade_and_call
let calldata = array![VALUE2];
v1.upgrade_and_call(V2_CLASS_HASH(), SET_VALUE2_SELECTOR, calldata.span());

// Check value2 is set
let v2 = IUpgradesV2Dispatcher { contract_address: v1.contract_address };
assert(v2.get_value2() == VALUE2, 'Should set val during upgrade');
}

#[test]
#[available_gas(2000000)]
#[should_panic(expected: ('ENTRYPOINT_NOT_FOUND', 'ENTRYPOINT_FAILED'))]
fn test_upgrade_and_call_with_removed_selector() {
let v1 = deploy_v1();
let calldata = array![];
v1.upgrade_and_call(V2_CLASS_HASH(), REMOVE_SELECTOR, calldata.span());
}
1 change: 1 addition & 0 deletions src/openzeppelin/upgrades.cairo
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
mod upgradeable;
Loading