Skip to content

Commit

Permalink
Pause Contract and Resume Contract (#779)
Browse files Browse the repository at this point in the history
<!--
Thanks for submitting a pull request! Here are some helpful tips:

* Always create branches on and target the `develop` branch.
* Run all the tests locally and ensure that they are passing.
* Run `make format` to ensure that the code is formatted.
* Run `make check` to ensure that all checks passed successfully.
* Small commits and contributions that attempt one single goal is
preferable.
* If the idea changes or adds anything functional which will affect
users, an
AIP discussion is required first on the Aurora forum: 

https://forum.aurora.dev/discussions/AIPs%20(Aurora%20Improvement%20Proposals).
* Avoid breaking the public API (namely in engine/src/lib.rs) unless
required.
* If your PR is a WIP, ensure that you enable "draft" mode.
* Your first PRs won't use the CI automatically unless a maintainer
starts.
If this is not your first PR, please do NOT abuse the CI resources.

Checklist:
- [ ] I have performed a self-review of my code
- [ ] I have documented my code, particularly in the hard-to-understand
areas
- [ ] I have made corresponding changes to the documentation
- [ ] I have added tests to prove my fix or new feature is effective and
works
- [ ] Any dependent changes have been merged
- [ ] The PR is targeting the `develop` branch and not `master`
- [ ] I have pre-squashed my commits into a single commit and rebased.
-->

## Description
Feature to pause and resume the engine contract.

We added two public methods to pause and resume the engine contract.
Only DAO can use these functionalities. We use an Engine State field as
a flag to indicate paused state. All existing contract public methods
check this flat before proceeding, and fail if contract is paused.

## Performance / NEAR gas cost considerations
All public methods would need to read the Engine State now, but no
impact on performance was visible.
<!--
Performance regressions are not ideal, though we welcome performance 
improvements. Any PR must be completely mindful of any gas cost
increases. The
CI will fail if the gas costs change at all. Do update these tests to 
accommodate for the new gas changes. It is good to explain 
this change, if necessary.
-->

## Testing
Added new tests to cover the DAO requirement and the feature effect. 
<!--
Please describe the tests that you ran to verify your changes.
-->

## How to review
Please, take a look at the public methods on the contract:
-All set methods should be affected by pausing.
-All get methods should NOT be affected by pausing.

## Additional information
This feature came as a necessity for the Aurora Hashchain integration.
The decided strategy is to:
1. upgrade the engine with the Hashchain inactive.
2. pause the contract.
3. call a special contract method added by the Hashchain upgrade, to
pass the seed computed Hashchain value for the block, and to activate
the Hashchain.
4. resume the contract.
5. upgrade the engine to remove unnecessary code and the special method.

There are more details about this but this is the general strategy.

<!--
Include any additional information which you think should be in this PR,
such
as prior arts, future extensions, unresolved problems, or a TODO list
which
should be followed up.
-->
  • Loading branch information
Casuso authored and joshuajbouw committed Jul 20, 2023
1 parent 1b56d98 commit a66fddb
Show file tree
Hide file tree
Showing 12 changed files with 286 additions and 9 deletions.
1 change: 1 addition & 0 deletions engine-standalone-storage/src/relayer_db/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ mod test {
chain_id: aurora_engine_types::types::u256_to_arr(&1_313_161_555.into()),
owner_id: "aurora".parse().unwrap(),
upgrade_delay_blocks: 0,
is_paused: false,
};

// Initialize engine and connector states in storage.
Expand Down
16 changes: 16 additions & 0 deletions engine-standalone-storage/src/sync/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -453,6 +453,22 @@ fn non_submit_execute<'db, M: ModExpAlgorithm + 'static>(
prev.upgrade_delay_blocks = args.upgrade_delay_blocks;
state::set_state(&mut io, &prev)?;

None
}
TransactionKind::PauseContract => {
let mut prev = state::get_state(&io)?;

prev.is_paused = true;
state::set_state(&mut io, &prev)?;

None
}
TransactionKind::ResumeContract => {
let mut prev = state::get_state(&io)?;

prev.is_paused = false;
state::set_state(&mut io, &prev)?;

None
}
};
Expand Down
12 changes: 12 additions & 0 deletions engine-standalone-storage/src/sync/types.rs
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,10 @@ pub enum TransactionKind {
FactoryUpdateAddressVersion(AddressVersionUpdateArgs),
FactorySetWNearAddress(Address),
FundXccSubAccound(FundXccArgs),
/// Pause the contract
PauseContract,
/// Resume the contract
ResumeContract,
/// Sentinel kind for cases where a NEAR receipt caused a
/// change in Aurora state, but we failed to parse the Action.
Unknown,
Expand Down Expand Up @@ -354,6 +358,8 @@ impl TransactionKind {
Self::SetOwner(_) => Self::no_evm_execution("set_owner"),
Self::SetUpgradeDelayBlocks(_) => Self::no_evm_execution("set_upgrade_delay_blocks"),
Self::FundXccSubAccound(_) => Self::no_evm_execution("fund_xcc_sub_account"),
Self::PauseContract => Self::no_evm_execution("pause_contract"),
Self::ResumeContract => Self::no_evm_execution("resume_contract"),
}
}

Expand Down Expand Up @@ -523,6 +529,8 @@ enum BorshableTransactionKind<'a> {
SubmitWithArgs(Cow<'a, parameters::SubmitArgs>),
FundXccSubAccound(Cow<'a, FundXccArgs>),
SetUpgradeDelayBlocks(Cow<'a, parameters::SetUpgradeDelayBlocksArgs>),
PauseContract,
ResumeContract,
}

impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> {
Expand Down Expand Up @@ -569,6 +577,8 @@ impl<'a> From<&'a TransactionKind> for BorshableTransactionKind<'a> {
TransactionKind::SetUpgradeDelayBlocks(x) => {
Self::SetUpgradeDelayBlocks(Cow::Borrowed(x))
}
TransactionKind::PauseContract => Self::PauseContract,
TransactionKind::ResumeContract => Self::ResumeContract,
}
}
}
Expand Down Expand Up @@ -634,6 +644,8 @@ impl<'a> TryFrom<BorshableTransactionKind<'a>> for TransactionKind {
BorshableTransactionKind::SetUpgradeDelayBlocks(x) => {
Ok(Self::SetUpgradeDelayBlocks(x.into_owned()))
}
BorshableTransactionKind::PauseContract => Ok(Self::PauseContract),
BorshableTransactionKind::ResumeContract => Ok(Self::ResumeContract),
}
}
}
4 changes: 4 additions & 0 deletions engine-tests/src/test_utils/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ pub const PAUSED_PRECOMPILES: &str = "paused_precompiles";
pub const RESUME_PRECOMPILES: &str = "resume_precompiles";
pub const SET_OWNER: &str = "set_owner";
pub const SET_UPGRADE_DELAY_BLOCKS: &str = "set_upgrade_delay_blocks";
pub const PAUSE_CONTRACT: &str = "pause_contract";
pub const RESUME_CONTRACT: &str = "resume_contract";

const CALLER_ACCOUNT_ID: &str = "some-account.near";

Expand Down Expand Up @@ -245,6 +247,8 @@ impl AuroraRunner {
|| method_name == RESUME_PRECOMPILES
|| method_name == SET_OWNER
|| method_name == SET_UPGRADE_DELAY_BLOCKS
|| method_name == PAUSE_CONTRACT
|| method_name == RESUME_CONTRACT
{
standalone_runner.submit_raw(method_name, &self.context, &self.promise_results)?;
self.validate_standalone();
Expand Down
32 changes: 32 additions & 0 deletions engine-tests/src/test_utils/standalone/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -332,6 +332,38 @@ impl StandaloneRunner {
self.cumulative_diff.append(outcome.diff.clone());
storage::commit(storage, &outcome);

Ok(SubmitResult::new(
TransactionStatus::Succeed(Vec::new()),
0,
Vec::new(),
))
} else if method_name == test_utils::PAUSE_CONTRACT {
let transaction_hash = aurora_engine_sdk::keccak(&ctx.input);
let mut tx_msg =
Self::template_tx_msg(storage, &env, 0, transaction_hash, promise_results);
tx_msg.transaction = TransactionKind::PauseContract;

let outcome =
sync::execute_transaction_message::<AuroraModExp>(storage, tx_msg).unwrap();
self.cumulative_diff.append(outcome.diff.clone());
storage::commit(storage, &outcome);

Ok(SubmitResult::new(
TransactionStatus::Succeed(Vec::new()),
0,
Vec::new(),
))
} else if method_name == test_utils::RESUME_CONTRACT {
let transaction_hash = aurora_engine_sdk::keccak(&ctx.input);
let mut tx_msg =
Self::template_tx_msg(storage, &env, 0, transaction_hash, promise_results);
tx_msg.transaction = TransactionKind::ResumeContract;

let outcome =
sync::execute_transaction_message::<AuroraModExp>(storage, tx_msg).unwrap();
self.cumulative_diff.append(outcome.diff.clone());
storage::commit(storage, &outcome);

Ok(SubmitResult::new(
TransactionStatus::Succeed(Vec::new()),
0,
Expand Down
1 change: 1 addition & 0 deletions engine-tests/src/tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ pub mod modexp;
mod multisender;
mod one_inch;
mod pausable_precompiles;
mod pause_contract;
mod prepaid_gas_precompile;
mod promise_results_precompile;
mod random;
Expand Down
118 changes: 118 additions & 0 deletions engine-tests/src/tests/pause_contract.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
use crate::test_utils;
use aurora_engine::parameters::SetUpgradeDelayBlocksArgs;
use borsh::BorshSerialize;

#[test]
fn test_pause_contract_require_owner() {
let mut runner = test_utils::deploy_evm();
let aurora_account_id = runner.aurora_account_id.clone();

let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("resume_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("pause_contract", "new_owner.near", vec![]);
assert!(result.is_err());
}

#[test]
fn test_resume_contract_require_owner() {
let mut runner = test_utils::deploy_evm();
let aurora_account_id = runner.aurora_account_id.clone();

let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("resume_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("resume_contract", "new_owner.near", vec![]);
assert!(result.is_err());
}

#[test]
fn test_pause_contract_require_running() {
let mut runner = test_utils::deploy_evm();
let aurora_account_id = runner.aurora_account_id.clone();

let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_err());
}

#[test]
fn test_resume_contract_require_paused() {
let mut runner = test_utils::deploy_evm();
let aurora_account_id = runner.aurora_account_id.clone();

let result = runner.call("resume_contract", &aurora_account_id, vec![]);
assert!(result.is_err());

let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("resume_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());
}

#[test]
fn test_pause_contract() {
let mut runner = test_utils::deploy_evm();
let aurora_account_id = runner.aurora_account_id.clone();
let set = SetUpgradeDelayBlocksArgs {
upgrade_delay_blocks: 2,
}
.try_to_vec()
.unwrap();

// contract is running by default, gets and sets should work
let result = runner.call("get_upgrade_delay_blocks", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("set_upgrade_delay_blocks", &aurora_account_id, set.clone());
assert!(result.is_ok());

// pause contract
let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

// contract is paused, gets should still work but sets should fail
let result = runner.call("get_upgrade_delay_blocks", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("set_upgrade_delay_blocks", &aurora_account_id, set);
assert!(result.is_err());
}

#[test]
fn test_resume_contract() {
let mut runner = test_utils::deploy_evm();
let aurora_account_id = runner.aurora_account_id.clone();
let set = SetUpgradeDelayBlocksArgs {
upgrade_delay_blocks: 2,
}
.try_to_vec()
.unwrap();

// pause contract
let result = runner.call("pause_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

// resume contract
let result = runner.call("resume_contract", &aurora_account_id, vec![]);
assert!(result.is_ok());

// contract is running again, gets and sets should work
let result = runner.call("get_upgrade_delay_blocks", &aurora_account_id, vec![]);
assert!(result.is_ok());

let result = runner.call("set_upgrade_delay_blocks", &aurora_account_id, set);
assert!(result.is_ok());
}
4 changes: 2 additions & 2 deletions engine-tests/src/tests/sanity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -147,9 +147,9 @@ fn test_state_format() {
};
let state: aurora_engine::state::EngineState = args.into();
let expected_hex: String = [
"01000000000000000000000000000000000000000000000000000000000000029a",
"02000000000000000000000000000000000000000000000000000000000000029a",
"04000000626f7373",
"0300000000000000",
"030000000000000000",
]
.concat();
assert_eq!(hex::encode(state.borsh_serialize().unwrap()), expected_hex);
Expand Down
1 change: 1 addition & 0 deletions engine-tests/src/tests/standalone/sanity.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ fn test_deploy_code() {
chain_id,
owner_id: owner_id.clone(),
upgrade_delay_blocks: 0,
is_paused: false,
};
let origin = Address::new(H160([0u8; 20]));
let storage = RefCell::new(Storage::default());
Expand Down
2 changes: 2 additions & 0 deletions engine/src/errors.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@ pub const ERR_VERIFY_PROOF: &[u8; 16] = b"ERR_VERIFY_PROOF";
pub const ERR_INVALID_UPGRADE: &[u8; 19] = b"ERR_INVALID_UPGRADE";
pub const ERR_NO_UPGRADE: &[u8; 14] = b"ERR_NO_UPGRADE";
pub const ERR_NOT_ALLOWED: &[u8; 15] = b"ERR_NOT_ALLOWED";
pub const ERR_PAUSED: &[u8; 10] = b"ERR_PAUSED";
pub const ERR_RUNNING: &[u8; 11] = b"ERR_RUNNING";

pub const ERR_SERIALIZE: &str = "ERR_SERIALIZE";
pub const ERR_PROMISE_ENCODING: &str = "ERR_PROMISE_ENCODING";
Expand Down
Loading

0 comments on commit a66fddb

Please sign in to comment.