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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
/target
Cargo.lock
near-plugins/tests/contracts/*/target
examples/target

# Ignore IDE data
.vscode/
Expand Down
6 changes: 5 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ Documentation of all methods provided by `Pausable` is available in the [definit

### [Upgradable](/near-plugins/src/upgradable.rs)

Allows a contract to be upgraded by owner without having a Full Access Key.
Allows a contract to be upgraded by owner with delay and without having a Full Access Key.

Contract example using _Upgradable_ plugin. Note that it requires the contract to be Ownable.

Expand All @@ -77,6 +77,7 @@ impl Counter {
fn new() -> Self {
let mut contract = Self {};
contract.owner_set(Some(near_sdk::env::predecessor_account_id()));
contract.up_init_staging_duration(std::time::Duration::from_secs(60).as_nanos().try_into().unwrap()); // 1 minute
contract
}
}
Expand All @@ -85,6 +86,9 @@ impl Counter {
To upgrade the contract first call `up_stage_code` passing the binary as first argument serialized as borsh. Then call `up_deploy_code`.
This functions must be called from the owner.

To update the staging delay first call `up_stage_update_staging_duration` passing the new delay duration. Then call `up_apply_update_staging_duration`.
This functions must be called from the owner.

Documentation of all methods provided by the derived implementation of `Upgradable` is available in the [definition of the trait](/near-plugins/src/upgradable.rs). More examples and guidelines for interacting with an `Upgradable` contract can be found [here](/examples/upgradable-examples/README.md).

### [AccessControllable](/near-plugins/src/access_controllable.rs)
Expand Down
22 changes: 11 additions & 11 deletions examples/upgradable-examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,26 +66,26 @@ To upgrade the contract first call up_stage_code passing the binary as first arg

## The contract methods description
### up_storage_key
`up_storage_key` is a _view_ method that returns a key of the storage slot for stage code.
By default, `b"__CODE__"` is used. For changing, the attribute `upgradable` can be used.
`up_storage_prefix` is a _view_ method that returns the storage prefix for slots related to upgradable.
By default, `b"__up__"` is used. For changing, the attribute `upgradable` can be used.

```shell
$ near view <CONTRACT_ACCOUNT> up_storage_key
View call: <CONTRACT_ACCOUNT>.up_storage_key()
$ near view <CONTRACT_ACCOUNT> up_storage_prefix
View call: <CONTRACT_ACCOUNT>.up_storage_prefix()
[
95, 95, 80, 65, 85,
83, 69, 68, 95, 95
95, 95, 117,
112, 95, 95
]
$ python3
>>> print(' '.join(str(b) for b in bytes("__CODE__", 'utf8')))
95 95 67 79 68 69 95 95
>>> print(' '.join(str(b) for b in bytes("__up__", 'utf8')))
95 95 117 112 95 95
```

Example of changing paused storage key:
Example of changing the storage prefix:
```rust
#[near_bindgen]
#[derive(Ownable, Upgradable, Default, BorshSerialize, BorshDeserialize)]
#[upgradable(code_storage_key="OTHER_CODE_STORAGE_KEY")]
#[upgradable(storage_prefix="OTHER_CODE_STORAGE_PREFIX")]
struct Counter {
counter: u64,
}
Expand All @@ -105,7 +105,7 @@ But it doesn't work in that way because we can't provide in Bash so long args...
For running `up_satge_code` take a look on `up_stage_code/src/main.rs` script.
```shell
$ cd up_stage_code
$ cargo run -- "<PATH_TO_KEY_FOR_CONTRACT_ACCOUNT>"
$ cargo run -- -p '<PATH_TO_KEY_FOR_CONTRACT_ACCOUNT>'
$ cd ..
```
Where `<PATH_TO_KEY_FOR_CONTRACT_ACCOUNT>` is `$HOME/.near-credentials/testnet/<CONTRACT_ACCOUNT>.json`
Expand Down
7 changes: 5 additions & 2 deletions examples/upgradable-examples/up_stage_code/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,12 @@ async fn main() {

let contract: Account = match &*args.network {
"testnet" => get_contract!(testnet, args.path_to_key),
"mainnet" => get_contract!(mainnet, args.path_to_key),
"mainnet" => get_contract!(mainnet, args.path_to_key),
"betanet" => get_contract!(betanet, args.path_to_key),
network => panic!("Unknown network {}. Possible networks: testnet, mainnet, betanet", network)
network => panic!(
"Unknown network {}. Possible networks: testnet, mainnet, betanet",
network
),
};

let wasm = std::fs::read(&args.wasm).unwrap();
Expand Down
1 change: 1 addition & 0 deletions examples/upgradable-examples/upgradable_base/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ impl Counter {
pub fn new() -> Self {
let mut contract = Self { counter: 0 };
contract.owner_set(Some(near_sdk::env::predecessor_account_id()));
contract.up_init_staging_duration(std::time::Duration::from_secs(60).as_nanos().try_into().unwrap()); // 1 minute
contract
}

Expand Down
135 changes: 126 additions & 9 deletions near-plugins-derive/src/upgradable.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,11 @@ use syn::{parse_macro_input, DeriveInput};
#[derive(FromDeriveInput, Default)]
#[darling(default, attributes(upgradable), forward_attrs(allow, doc, cfg))]
struct Opts {
code_storage_key: Option<String>,
storage_prefix: Option<String>,
}

const DEFAULT_STORAGE_PREFIX: &str = "__up__";

/// Generates the token stream for the `Upgradable` macro.
pub fn derive_upgradable(input: TokenStream) -> TokenStream {
let cratename = cratename();
Expand All @@ -18,29 +20,94 @@ pub fn derive_upgradable(input: TokenStream) -> TokenStream {
let opts = Opts::from_derive_input(&input).expect("Wrong options");
let DeriveInput { ident, .. } = input;

let code_storage_key = opts
.code_storage_key
.unwrap_or_else(|| "__CODE__".to_string());
let storage_prefix = opts
.storage_prefix
.unwrap_or_else(|| DEFAULT_STORAGE_PREFIX.to_string());

let output = quote! {
/// Used to make storage prefixes unique. Not to be used directly,
/// instead it should be prepended to the storage prefix specified by
/// the user.
#[derive(::near_sdk::borsh::BorshSerialize)]
enum __UpgradableStorageKey {
Code,
StagingTimestamp,
StagingDuration,
NewStagingDuration,
NewStagingDurationTimestamp,
}

impl #ident {
fn up_get_timestamp(&self, key: __UpgradableStorageKey) -> Option<::near_sdk::Timestamp> {
near_sdk::env::storage_read(self.up_storage_key(key).as_ref()).map(|timestamp_bytes| {
::near_sdk::Timestamp::try_from_slice(&timestamp_bytes).unwrap_or_else(|_|
near_sdk::env::panic_str("Upgradable: Invalid u64 timestamp format")
)
})
}

fn up_get_duration(&self, key: __UpgradableStorageKey) -> Option<::near_sdk::Duration> {
near_sdk::env::storage_read(self.up_storage_key(key).as_ref()).map(|duration_bytes| {
::near_sdk::Duration::try_from_slice(&duration_bytes).unwrap_or_else(|_|
near_sdk::env::panic_str("Upgradable: Invalid u64 Duration format")
)
})
}

fn up_set_timestamp(&self, key: __UpgradableStorageKey, value: ::near_sdk::Timestamp) {
self.up_storage_write(key, &value.try_to_vec().unwrap());
}

fn up_set_duration(&self, key: __UpgradableStorageKey, value: ::near_sdk::Duration) {
self.up_storage_write(key, &value.try_to_vec().unwrap());
}

fn up_storage_key(&self, key: __UpgradableStorageKey) -> Vec<u8> {
let key_vec = key
.try_to_vec()
.unwrap_or_else(|_| ::near_sdk::env::panic_str("Storage key should be serializable"));
[(#storage_prefix).as_bytes(), key_vec.as_slice()].concat()
}

fn up_storage_write(&self, key: __UpgradableStorageKey, value: &[u8]) {
near_sdk::env::storage_write(self.up_storage_key(key).as_ref(), &value);
}

fn up_set_staging_duration_unchecked(&self, staging_duration: near_sdk::Duration) {
self.up_storage_write(__UpgradableStorageKey::StagingDuration, &staging_duration.try_to_vec().unwrap());
}
}

#[near_bindgen]
impl Upgradable for #ident {
fn up_storage_key(&self) -> Vec<u8>{
(#code_storage_key).as_bytes().to_vec()
fn up_storage_prefix(&self) -> &'static [u8] {
(#storage_prefix).as_bytes()
}

fn up_get_delay_status(&self) -> #cratename::UpgradableDurationStatus {
near_plugins::UpgradableDurationStatus {
staging_duration: self.up_get_duration(__UpgradableStorageKey::StagingDuration),
staging_timestamp: self.up_get_timestamp(__UpgradableStorageKey::StagingTimestamp),
new_staging_duration: self.up_get_duration(__UpgradableStorageKey::NewStagingDuration),
new_staging_duration_timestamp: self.up_get_timestamp(__UpgradableStorageKey::NewStagingDurationTimestamp),
}
}

#[#cratename::only(owner)]
fn up_stage_code(&mut self, #[serializer(borsh)] code: Vec<u8>) {
if code.is_empty() {
near_sdk::env::storage_remove(self.up_storage_key().as_ref());
near_sdk::env::storage_remove(self.up_storage_key(__UpgradableStorageKey::Code).as_ref());
Comment thread
mooori marked this conversation as resolved.
near_sdk::env::storage_remove(self.up_storage_key(__UpgradableStorageKey::StagingTimestamp).as_ref());
} else {
near_sdk::env::storage_write(self.up_storage_key().as_ref(), code.as_ref());
let timestamp = near_sdk::env::block_timestamp() + self.up_get_duration(__UpgradableStorageKey::StagingDuration).unwrap_or(0);
self.up_storage_write(__UpgradableStorageKey::Code, &code);
self.up_set_timestamp(__UpgradableStorageKey::StagingTimestamp, timestamp);
}
}

#[result_serializer(borsh)]
fn up_staged_code(&self) -> Option<Vec<u8>> {
near_sdk::env::storage_read(self.up_storage_key().as_ref())
near_sdk::env::storage_read(self.up_storage_key(__UpgradableStorageKey::Code).as_ref())
}

fn up_staged_code_hash(&self) -> Option<::near_sdk::CryptoHash> {
Expand All @@ -50,9 +117,59 @@ pub fn derive_upgradable(input: TokenStream) -> TokenStream {

#[#cratename::only(owner)]
fn up_deploy_code(&mut self) -> near_sdk::Promise {
let staging_timestamp = self.up_get_timestamp(__UpgradableStorageKey::StagingTimestamp)
.unwrap_or_else(|| ::near_sdk::env::panic_str("Upgradable: staging timestamp isn't set"));

if near_sdk::env::block_timestamp() < staging_timestamp {
near_sdk::env::panic_str(
format!(
"Upgradable: Deploy code too early: staging ends on {}",
staging_timestamp
)
.as_str(),
);
}

near_sdk::Promise::new(near_sdk::env::current_account_id())
.deploy_contract(self.up_staged_code().unwrap_or_else(|| ::near_sdk::env::panic_str("Upgradable: No staged code")))
}

#[#cratename::only(owner)]
fn up_init_staging_duration(&mut self, staging_duration: near_sdk::Duration) {
near_sdk::require!(self.up_get_duration(__UpgradableStorageKey::StagingDuration).is_none(), "Upgradable: staging duration was already initialized");
self.up_set_staging_duration_unchecked(staging_duration);
Comment thread
mooori marked this conversation as resolved.
}

#[#cratename::only(owner)]
fn up_stage_update_staging_duration(&mut self, staging_duration: near_sdk::Duration) {
let current_staging_duration = self.up_get_duration(__UpgradableStorageKey::StagingDuration)
.unwrap_or_else(|| ::near_sdk::env::panic_str("Upgradable: staging duration isn't initialized"));

self.up_set_duration(__UpgradableStorageKey::NewStagingDuration, staging_duration);
let staging_duration_timestamp = near_sdk::env::block_timestamp() + current_staging_duration;
self.up_set_timestamp(__UpgradableStorageKey::NewStagingDurationTimestamp, staging_duration_timestamp);
}

#[#cratename::only(owner)]
fn up_apply_update_staging_duration(&mut self) {
let staging_timestamp = self.up_get_timestamp(__UpgradableStorageKey::NewStagingDurationTimestamp)
.unwrap_or_else(|| ::near_sdk::env::panic_str("Upgradable: No staged update"));

if near_sdk::env::block_timestamp() < staging_timestamp {
near_sdk::env::panic_str(
format!(
"Upgradable: Update duration too early: staging ends on {}",
staging_timestamp
)
.as_str(),
);
}

let new_duration = self.up_get_duration(__UpgradableStorageKey::NewStagingDuration)
.unwrap_or_else(|| ::near_sdk::env::panic_str("Upgradable: No staged duration update"));

self.up_set_duration(__UpgradableStorageKey::StagingDuration, new_duration);
}
}
};

Expand Down
1 change: 1 addition & 0 deletions near-plugins/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ pub use near_plugins_derive::{
pub use ownable::Ownable;
pub use pausable::Pausable;
pub use upgradable::Upgradable;
pub use upgradable::UpgradableDurationStatus;

// Re-exporting these dependencies avoids requiring contracts to depend on them.
// For example, without re-exporting `bitflags` a contract using the access
Expand Down
Loading