diff --git a/mapclients/near/README.md b/mapclients/near/README.md index 22731703..ef7bd5fe 100644 --- a/mapclients/near/README.md +++ b/mapclients/near/README.md @@ -69,6 +69,25 @@ EPOCH_ID=300 # get the information of this epoch id to initialize the MAP light ./scripts/deploy.sh ``` +## Upgrade the contract + +**NOTE**: currently the script works on MacOS only. Below `scripts` is in directory `../../mcs/near/map-cross-chain-service/`. +```shell +MAP_CLIENT_WASM_FILE=/path/to/map/client/contract # new MAP light client contract wasm file + +# request to upgrade MAP light client contract by multisig member +./scripts/manage_multisig.sh request_and_confirm upgrade_map_client $MAP_CLIENT_WASM_FILE ${MEMBERS[1]} + +# the request ID can be obtained from the last line of last command's output +REQUEST_ID= + +# confirm the request by another member +./scripts/manage_multisig.sh confirm $REQUEST_ID ${MEMBERS[2]} + +# if the request is not executed because of the time lock, anyone can execute it after REQUEST_LOCK time +# ./scripts/manage_multisig.sh execute $REQUEST_ID $MASTER_ACCOUNT +``` + ## Testing diff --git a/mapclients/near/contracts/src/lib.rs b/mapclients/near/contracts/src/lib.rs index 95d2da19..f290ed78 100644 --- a/mapclients/near/contracts/src/lib.rs +++ b/mapclients/near/contracts/src/lib.rs @@ -11,10 +11,10 @@ pub mod traits; mod hash; use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; -use near_sdk::{env, log, near_bindgen, PanicOnDefault, serde_json}; +use near_sdk::{AccountId, env, log, near_bindgen, PanicOnDefault, serde_json, Gas}; use near_sdk::collections::UnorderedMap; use near_sdk::env::keccak256; -use near_sdk::json_types::U64; +use near_sdk::json_types::{Base64VecU8, U64}; use near_sdk::serde::{Serialize, Deserialize}; pub use crypto::{G1, G2, REGISTER_EXPECTED_ERR}; use crate::types::{istanbul::IstanbulExtra, istanbul::get_epoch_number, header::Header}; @@ -27,6 +27,7 @@ use crate::types::proof::{ReceiptProof, verify_trie_proof}; const ECDSA_SIG_LENGTH: usize = 65; const ECDSA_REGISTER: u64 = 2; const MAX_RECORD: u64 = 20; +const GAS_FOR_UPGRADE_SELF_DEPLOY: Gas = Gas(15_000_000_000_000); #[near_bindgen] #[derive(BorshDeserialize, BorshSerialize, PanicOnDefault)] @@ -34,6 +35,7 @@ pub struct MapLightClient { epoch_records: UnorderedMap, epoch_size: u64, header_height: u64, + owner: AccountId, } #[derive(BorshDeserialize, BorshSerialize, Serialize, Deserialize, Clone, Debug)] @@ -59,7 +61,8 @@ impl MapLightClient { pub fn new(threshold: U64, validators: Vec, epoch: U64, - epoch_size: U64) -> Self { + epoch_size: U64, + owner: AccountId) -> Self { assert!(!Self::initialized(), "already initialized"); assert_ne!(0, validators.len(), "empty validators!"); assert_ne!(0, threshold.0, "threashold should not be 0"); @@ -85,9 +88,17 @@ impl MapLightClient { epoch_records: val_records, epoch_size: epoch_size.into(), header_height: (epoch.0 - 1) * epoch_size.0, + owner, } } + #[private] + #[init(ignore_state)] + pub fn migrate_client() -> Self { + let client: MapLightClient = env::state_read().expect("ERR_CONTRACT_IS_NOT_INITIALIZED"); + client + } + pub fn initialized() -> bool { env::state_read::().is_some() } @@ -264,4 +275,24 @@ impl MapLightClient { self.epoch_records.remove(&epoch_to_remove); } } + + pub fn update_owner(&mut self, new_owner: AccountId) { + assert_eq!(self.owner, env::predecessor_account_id(), "unexpected caller {}", env::predecessor_account_id()); + self.owner = new_owner; + } + + pub fn upgrade_client(&mut self, code: Base64VecU8) { + assert_eq!(self.owner, env::predecessor_account_id(), "unexpected caller {}", env::predecessor_account_id()); + + let current_id = env::current_account_id(); + let promise_id = env::promise_batch_create(¤t_id); + env::promise_batch_action_deploy_contract(promise_id, &code.0); + env::promise_batch_action_function_call( + promise_id, + "migrate_client", + &[], + 0, + env::prepaid_gas() - env::used_gas() - GAS_FOR_UPGRADE_SELF_DEPLOY, + ); + } } \ No newline at end of file diff --git a/mapclients/near/map-client-factory/src/lib.rs b/mapclients/near/map-client-factory/src/lib.rs index 6e913062..798a2afb 100644 --- a/mapclients/near/map-client-factory/src/lib.rs +++ b/mapclients/near/map-client-factory/src/lib.rs @@ -1,6 +1,6 @@ use near_sdk::borsh::{self, BorshDeserialize, BorshSerialize}; use near_sdk::serde_json::json; -use near_sdk::{env, near_bindgen, Promise, Gas}; +use near_sdk::{env, near_bindgen, Promise, Gas, AccountId}; use near_sdk::json_types::U64; use map_light_client::Validator; @@ -22,7 +22,8 @@ impl Factory { threshold: U64, validators: Vec, epoch: U64, - epoch_size: U64 + epoch_size: U64, + owner: AccountId ) -> Promise { let account_id = format!("{}.{}", name, env::current_account_id()); Promise::new(account_id.parse().unwrap()) @@ -35,7 +36,8 @@ impl Factory { "threshold": threshold, "validators": validators, "epoch": epoch, - "epoch_size": epoch_size }) + "epoch_size": epoch_size, + "owner": owner}) .to_string() .as_bytes() .to_vec(), diff --git a/mapclients/near/scripts/config.sh b/mapclients/near/scripts/config.sh index 51271e87..33b5bfac 100644 --- a/mapclients/near/scripts/config.sh +++ b/mapclients/near/scripts/config.sh @@ -2,4 +2,5 @@ MASTER_ACCOUNT=maplabs.testnet # make sure the account is already created on NEA FACTORY_NAME=cfac2 # the name of map client factory contract to be created, the account ID will be $FACTORY_NAME.$MASTER_ACCOUNT CLIENT_NAME=client # the name of MAP light client contract to be created, the account ID will be $CLIENT_NAME.$FACTORY_NAME MAP_RPC_URL=https://testnet-rpc.maplabs.io # the RPC url of MAP blockchain -EPOCH_ID=6 # get the information of this epoch id to initialize the MAP light client contract \ No newline at end of file +EPOCH_ID=6 # get the information of this epoch id to initialize the MAP light client contract +OWNER=multisig.mfac.maplabs.testnet # the owner of the MAP light client, which is a multisig-timelock contract \ No newline at end of file diff --git a/mapclients/near/scripts/deploy.sh b/mapclients/near/scripts/deploy.sh index 1925eee0..dcae9005 100755 --- a/mapclients/near/scripts/deploy.sh +++ b/mapclients/near/scripts/deploy.sh @@ -7,6 +7,7 @@ source $SCRIPT_DIR/config.sh RESPONSE=`curl -X POST $MAP_RPC_URL -H "Content-Type: application/json" --data '{"jsonrpc":"2.0","method":"istanbul_getEpochInfo","params":['$EPOCH_ID'],"id":1}'` INIT_ARGS_CLIENT=`echo $RESPONSE | jq .result | jq --arg name $CLIENT_NAME '.name=$name'` +INIT_ARGS_CLIENT=`echo $INIT_ARGS_CLIENT | jq --args '.owner = "'$OWNER'"'` FACTORY_ACCOUNT=$FACTORY_NAME.$MASTER_ACCOUNT echo $MASTER_ACCOUNT diff --git a/mapclients/near/tests/data/init_value.json b/mapclients/near/tests/data/init_value.json index 4dda92fc..b9005abe 100644 --- a/mapclients/near/tests/data/init_value.json +++ b/mapclients/near/tests/data/init_value.json @@ -47,5 +47,6 @@ "_BLSG1PublicKey": "0x28681fcac6825e2a6711b2ef0d3a22eae527c41ecccdeb4e69dfff4002219d8b131f98eaf9323bf171e947401f0e6b1951f4c8f8aa525b677f1c811c88358e37", "_UncompressedBLSPublicKey": "Gsq81OIpjJHhdSYgqemenElRPq694arTgb8pRqQCFPAHHHKgLpEM8DGPlU49CViK9NDqX+xloMZRGwy2Qj3HbQbg14f6wraD+m8Z9zTfvdi94cuSkkaVZ52B4mXN7TfNB6Or5T6C+VfbkqTcJ5o7elWRx3X8H7eagYXZW7uTsrs=" } - ] + ], + "owner": "multisig.test.near" } \ No newline at end of file diff --git a/mapclients/near/tests/main.rs b/mapclients/near/tests/main.rs index 8b4eb79e..ebd99105 100644 --- a/mapclients/near/tests/main.rs +++ b/mapclients/near/tests/main.rs @@ -65,7 +65,8 @@ const INIT_VALUE: &str = r#"{ } ], "epoch":"1", - "epoch_size":"1000" + "epoch_size":"1000", + "owner": "multisig.test.near" }"#; const HEADER_0_012: &str = r#"{ @@ -143,6 +144,56 @@ async fn test_initialize() -> anyhow::Result<()> { Ok(()) } +#[tokio::test] +async fn test_manage_owner() -> anyhow::Result<()> { + let (worker, contract) = deploy_contract().await?; + let account = worker.dev_create_account().await?; + + let mut init_args: serde_json::Value = serde_json::from_str(INIT_VALUE).unwrap(); + init_args["owner"] = json!(contract.id()); + + let res = contract + .call(&worker, "new") + .args_json(json!(init_args))? + .gas(300_000_000_000_000) + .transact() + .await?; + + assert!(res.is_success(), "new contract failed"); + + let res = contract + .call(&worker, "update_owner") + .args_json(json!({ + "new_owner": account.id() + }))? + .gas(300_000_000_000_000) + .transact() + .await?; + assert!(res.is_success(), "update_owner failed"); + + let res = contract + .call(&worker, "update_owner") + .args_json(json!({ + "new_owner": account.id() + }))? + .gas(300_000_000_000_000) + .transact() + .await; + assert!(res.is_err(), "update_owner should fail"); + + let res = account + .call(&worker, contract.id(), "update_owner") + .args_json(json!({ + "new_owner": contract.id() + }))? + .gas(300_000_000_000_000) + .transact() + .await?; + assert!(res.is_success(), "update_owner should succeed"); + + Ok(()) +} + #[tokio::test] async fn test_update_block_header() -> anyhow::Result<()> { let (worker, contract) = deploy_contract().await?; diff --git a/mcs/near/scripts/config.sh b/mcs/near/scripts/config.sh index df73a3d7..b0b42bb1 100644 --- a/mcs/near/scripts/config.sh +++ b/mcs/near/scripts/config.sh @@ -21,4 +21,5 @@ export MCS_FACTORY_ACCOUNT=$MCS_FACTORY_NAME.$MASTER_ACCOUNT export MCS_ACCOUNT=$MCS_NAME.$MCS_FACTORY_ACCOUNT export MULTISIG_ACCOUNT=$MULTISIG_NAME.$MCS_FACTORY_ACCOUNT export MEMBERS -export MASTER_ACCOUNT \ No newline at end of file +export MASTER_ACCOUNT +export CLIENT_ACCOUNT \ No newline at end of file diff --git a/mcs/near/scripts/manage_multisig.sh b/mcs/near/scripts/manage_multisig.sh index ec6a30a4..c25a54ac 100755 --- a/mcs/near/scripts/manage_multisig.sh +++ b/mcs/near/scripts/manage_multisig.sh @@ -23,6 +23,7 @@ function printHelp() { echo " remove_mcs remove mcs token to_chain" echo " remove_ft remove fungible token to_chain" echo " upgrade_multisig upgrade multisig contract" + echo " upgrade_map_client upgrade map light client contract" echo " upgrade_mcs upgrade mcs contract" echo " upgrade_mcs_token upgrade mcs token contract" echo " set_client set new map light client account to mcs contract" @@ -186,6 +187,19 @@ function prepare_request() { exit 1 fi ;; + upgrade_map_client) + if [[ $# == 3 ]]; then + echo "upgrade map light client contract to $2" + RECEIVER=$CLIENT_ACCOUNT + METHOD="upgrade_client" + CODE=`base64 -i $2` + ARGS=`echo '{"code": "'$CODE'"}'| base64` + MEMBER=$3 + else + printHelp + exit 1 + fi + ;; upgrade_mcs) if [[ $# == 3 ]]; then echo "upgrade mcs contract to $2"