Skip to content

Commit

Permalink
feat: Upgrade factory from DAO
Browse files Browse the repository at this point in the history
feat: create proposal to upgrade factory

feat: upgrade factory from dao

feat: create script for upgrading factory from dao

feat: introduce upgrade_factory method

fix: remove new proposals

revert: factory manager update factory_contract method

fix: remove update factory method

restore old dao code

fix: dao wasm

modify scripts
  • Loading branch information
agileurbanite committed Dec 12, 2022
1 parent 1bdebad commit 90683cd
Show file tree
Hide file tree
Showing 3 changed files with 244 additions and 0 deletions.
140 changes: 140 additions & 0 deletions scripts/upgrade_factory_from_dao.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
#!/bin/bash
#### --------------------------------------------
#### NOTE: The following flows are supported in this file, for testing!
# - Create an UpgradeDAO via sputnikv2.testnet, funded with enough for 10 upgrades
# - Create an Upgradeable DAO via sputnikv2.testnet, for testing v2-v3 upgrade
# - UpgradeDAO proposal to store_blob on Upgradeable DAO
# - Upgradeable DAO proposal UpgradeSelf with hash from UpgradeDAO store_blob
# - Check code_hash on Upgradeable DAO
#### --------------------------------------------
set -e

# # TODO: Change to the official approved commit:
# COMMIT_V3=596f27a649c5df3310e945a37a41a957492c0322
# # git checkout $COMMIT_V3

# build the things
./build.sh

export NEAR_ENV=testnet
export FACTORY=testnet

if [ -z ${NEAR_ACCT+x} ]; then
# export NEAR_ACCT=sputnikv2.$FACTORY
export NEAR_ACCT=sputnikpm.$FACTORY
else
export NEAR_ACCT=$NEAR_ACCT
fi

# export FACTORY_ACCOUNT_ID=sputnikv2.$FACTORY
export FACTORY_ACCOUNT_ID=factory_1.$NEAR_ACCT
# export DAO_ACCOUNT_ID=croncat.sputnikv2.$FACTORY
export MAX_GAS=300000000000000
export GAS_100_TGAS=100000000000000
export GAS_150_TGAS=150000000000000
export GAS_220_TGAS=220000000000000
export BOND_AMOUNT=1
export BYTE_STORAGE_COST=10000000000000000000000000


# #### --------------------------------------------
# #### New Factory for entire test
# #### --------------------------------------------
near create-account $FACTORY_ACCOUNT_ID --masterAccount $NEAR_ACCT --initialBalance 80
# #### --------------------------------------------

# #### --------------------------------------------
# #### Build and deploy factory
# - Build and Deploy factory contract by running the following command from your current directory _(`sputnik-dao-contract/sputnikdao-factory2`)_:
# - Initialize factory
# #### --------------------------------------------
near deploy $FACTORY_ACCOUNT_ID --wasmFile=res/sputnikdao_factory2.wasm --accountId $FACTORY_ACCOUNT_ID
near call $FACTORY_ACCOUNT_ID new --accountId $FACTORY_ACCOUNT_ID --gas 100000000000000
# #### --------------------------------------------


#### --------------------------------------------
#### Deploy DAO that can upgrade factory
#### --------------------------------------------
export COUNCIL='["'$NEAR_ACCT'"]'
export TIMESTAMP=$(date +"%s")
export DAO_NAME=upgrademe-1-$TIMESTAMP
export DAO_ARGS=`echo '{"config": {"name": "'$DAO_NAME'", "purpose": "A dao that can upgrade a factory", "metadata":""}, "policy": '$COUNCIL'}' | base64`
near call $FACTORY_ACCOUNT_ID create "{\"name\": \"$DAO_NAME\", \"args\": \"$DAO_ARGS\"}" --accountId $FACTORY_ACCOUNT_ID --gas $GAS_150_TGAS --amount 12
export GENDAO=$DAO_NAME.$FACTORY_ACCOUNT_ID
#### --------------------------------------------


#### --------------------------------------------
#### Quick sanity check on getters
#### --------------------------------------------
near view $FACTORY_ACCOUNT_ID get_dao_list
#### --------------------------------------------

#### --------------------------------------------
#### Set owner of factory as DAO
#### --------------------------------------------
near call $FACTORY_ACCOUNT_ID set_owner '{"owner_id":"'$GENDAO'"}' --accountId $FACTORY_ACCOUNT_ID
#### --------------------------------------------

#### --------------------------------------------
#### Modify factory, rebuild, and create proposal to store
#### --------------------------------------------
# Store the code data
export FACTORYCODE='cat sputnikdao_factory2.wasm | base64'
# - most likely need to use near-cli-rs due to wasm string size limit
# Store blob in DAO
echo '{ "proposal": { "description": "Store upgrade", "kind": { "FunctionCall": { "receiver_id": "'$GENDAO'", "actions": [ { "method_name": "store_blob", "args": "'$(eval $FACTORYCODE)'", "deposit": "'$BYTE_STORAGE_COST'", "gas": "'$GAS_150_TGAS'" } ]}}}}' | base64 | pbcopy
# near call $GENDAO store $(eval "$FACTORYCODE") --base64 --accountId $FACTORY_ACCOUNT_ID --gas $GAS_100_TGAS --amount 10 > new_factory_hash.txt
# Once proposal created on Genesis DAO that is owner of factory account now, vote on it so that it can act_proposal storing the new factory code and returning a hash
# Vote on proposal
near call $GENDAO act_proposal '{"id": 0, "action" :"VoteApprove"}' --accountId $CONTRACT_ID --gas $MAX_GAS
# Act proposal might fail due to exceeded pre paid gas limit but factory is stored
# Set factory hash
export FACTORY_HASH=""

#### --------------------------------------------
#### Create proposal to upgrade factory to new factory hash
#### --------------------------------------------
near call $GENDAO add_proposal '{
"proposal": {
"description": "Upgrade to new factory hash using local stored code",
"kind": {
"UpgradeRemote": {
"receiver_id": "spudnike.testnet",
"method_name": "upgrade_factory",
"hash": "'$FACTORY_HASH'"
}
}
}
}' --accountId $CONTRACT_ID --amount $BOND_AMOUNT --gas $MAX_GAS
# Vote on proposal
near call $GENDAO act_proposal '{"id": 2, "action" :"VoteApprove"}' --accountId $CONTRACT_ID --gas $MAX_GAS

# Factory should be pointing to new hash

# Optionally store factory metadata
export PROPOSALARGS=`echo '{"factory_hash": "'$FACTORY_HASH'", "metadata": {"version": [1,0], "commit_id": ""}, "set_default": true}' | base64`
near call $GENDAO add_proposal '{
"proposal": {
"description": "Store factory metadata",
"kind": {
"FunctionCall": {
"receiver_id": "'$CONTRACT_ID'",
"actions": [
{
"method_name": "store_factory_metadata",
"args": "'$PROPOSALARGS'",
"deposit": "'$BYTE_STORAGE_COST'",
"gas": "'$GAS_220_TGAS'"
}
]
}
}
}
}' --accountId $CONTRACT_ID --amount $BOND_AMOUNT --gas $MAX_GAS

# Vote on storing factory metadata

near call $GENDAO act_proposal '{"id": 1, "action" :"VoteApprove"}' --accountId $CONTRACT_ID --gas $MAX_GAS
#### --------------------------------------------
Binary file modified sputnikdao-factory2/res/sputnikdao_factory2.wasm
Binary file not shown.
104 changes: 104 additions & 0 deletions sputnikdao-factory2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ type Version = [u8; 2];
// The keys used for writing data to storage via `env::storage_write`.
const DEFAULT_CODE_HASH_KEY: &[u8; 4] = b"CODE";
const FACTORY_OWNER_KEY: &[u8; 5] = b"OWNER";
const DEFAULT_FACTORY_HASH_KEY: &[u8; 7] = b"FACTORY";
const FACTORY_METADATA_KEY: &[u8; 7] = b"FACMETA";
const CODE_METADATA_KEY: &[u8; 8] = b"METADATA";

// The values used when writing initial data to the storage.
Expand All @@ -39,6 +41,19 @@ pub struct DaoContractMetadata {
pub changelog_url: Option<String>,
}

#[derive(BorshSerialize, BorshDeserialize, Serialize, Deserialize)]
#[cfg_attr(not(target_arch = "wasm32"), derive(Clone, Debug))]
#[serde(crate = "near_sdk::serde")]
pub struct DaoFactoryMetadata {
// version of the DAO factory code (e.g. [2, 0] -> 2.0, [3, 1] -> 3.1, [4, 0] -> 4.0)
pub version: Version,
// commit id of https://github.com/near-daos/sputnik-dao-contract
// representing a snapshot of the code that generated the wasm
pub commit_id: String,
// if available, url to the changelog to see the changes introduced in this version
pub changelog_url: Option<String>,
}

#[near_bindgen]
#[derive(BorshSerialize, BorshDeserialize, PanicOnDefault)]
pub struct SputnikDAOFactory {
Expand Down Expand Up @@ -291,6 +306,10 @@ impl SputnikDAOFactory {
slice_to_hash(&env::storage_read(DEFAULT_CODE_HASH_KEY).expect("Must have code hash"))
}

pub fn get_default_factory_hash(&self) -> Base58CryptoHash {
slice_to_hash(&env::storage_read(DEFAULT_FACTORY_HASH_KEY).expect("Must have code hash"))
}

pub fn get_default_version(&self) -> Version {
let storage_metadata = env::storage_read(CODE_METADATA_KEY).expect("INTERNAL_FAIL");
let deserialized_metadata: UnorderedMap<Base58CryptoHash, DaoContractMetadata> =
Expand All @@ -301,6 +320,16 @@ impl SputnikDAOFactory {
default_metadata.version
}

pub fn get_default_factory_version(&self) -> Version {
let storage_metadata = env::storage_read(CODE_METADATA_KEY).expect("INTERNAL_FAIL");
let deserialized_metadata: UnorderedMap<Base58CryptoHash, DaoFactoryMetadata> =
BorshDeserialize::try_from_slice(&storage_metadata).expect("INTERNAL_FAIL");
let default_metadata = deserialized_metadata
.get(&self.get_default_code_hash())
.expect("INTERNAL_FAIL");
default_metadata.version
}

/// Returns non serialized code by given code hash.
pub fn get_code(&self, code_hash: Base58CryptoHash) {
self.factory_manager.get_code(code_hash);
Expand Down Expand Up @@ -342,6 +371,42 @@ impl SputnikDAOFactory {
}
}

pub fn store_factory_metadata(
&self,
factory_hash: Base58CryptoHash,
metadata: DaoFactoryMetadata,
set_default: bool,
) {
self.assert_owner();
let hash: CryptoHash = factory_hash.into();
assert!(
env::storage_has_key(&hash),
"Factory not found for the given factory hash. Please store the factory code first."
);

let storage_metadata = env::storage_read(FACTORY_METADATA_KEY);
if storage_metadata.is_none() {
let mut storage_metadata: UnorderedMap<Base58CryptoHash, DaoFactoryMetadata> =
UnorderedMap::new(b"m".to_vec());
storage_metadata.insert(&factory_hash, &metadata);
let serialized_metadata =
BorshSerialize::try_to_vec(&storage_metadata).expect("INTERNAL_FAIL");
env::storage_write(FACTORY_METADATA_KEY, &serialized_metadata);
} else {
let storage_metadata = storage_metadata.expect("INTERNAL_FAIL");
let mut deserialized_metadata: UnorderedMap<Base58CryptoHash, DaoFactoryMetadata> =
BorshDeserialize::try_from_slice(&storage_metadata).expect("INTERNAL_FAIL");
deserialized_metadata.insert(&factory_hash, &metadata);
let serialized_metadata =
BorshSerialize::try_to_vec(&deserialized_metadata).expect("INTERNAL_FAIL");
env::storage_write(FACTORY_METADATA_KEY, &serialized_metadata);
}

if set_default {
env::storage_write(DEFAULT_FACTORY_HASH_KEY, &hash);
}
}

pub fn delete_contract_metadata(&self, code_hash: Base58CryptoHash) {
self.assert_owner();
let storage_metadata = env::storage_read(CODE_METADATA_KEY).expect("INTERNAL_FAIL");
Expand All @@ -353,13 +418,31 @@ impl SputnikDAOFactory {
env::storage_write(CODE_METADATA_KEY, &serialized_metadata);
}

pub fn delete_factory_metadata(&self, factory_hash: Base58CryptoHash) {
self.assert_owner();
let storage_metadata = env::storage_read(FACTORY_METADATA_KEY).expect("INTERNAL_FAIL");
let mut deserialized_metadata: UnorderedMap<Base58CryptoHash, DaoFactoryMetadata> =
BorshDeserialize::try_from_slice(&storage_metadata).expect("INTERNAL_FAIL");
deserialized_metadata.remove(&factory_hash);
let serialized_metadata =
BorshSerialize::try_to_vec(&deserialized_metadata).expect("INTERNAL_FAIL");
env::storage_write(FACTORY_METADATA_KEY, &serialized_metadata);
}

pub fn get_contracts_metadata(&self) -> Vec<(Base58CryptoHash, DaoContractMetadata)> {
let storage_metadata = env::storage_read(CODE_METADATA_KEY).expect("INTERNAL_FAIL");
let deserialized_metadata: UnorderedMap<Base58CryptoHash, DaoContractMetadata> =
BorshDeserialize::try_from_slice(&storage_metadata).expect("INTERNAL_FAIL");
return deserialized_metadata.to_vec();
}

pub fn get_factories_metadata(&self) -> Vec<(Base58CryptoHash, DaoFactoryMetadata)> {
let storage_metadata = env::storage_read(FACTORY_METADATA_KEY).expect("INTERNAL_FAIL");
let deserialized_metadata: UnorderedMap<Base58CryptoHash, DaoFactoryMetadata> =
BorshDeserialize::try_from_slice(&storage_metadata).expect("INTERNAL_FAIL");
return deserialized_metadata.to_vec();
}

fn assert_owner(&self) {
assert_eq!(
self.get_owner(),
Expand Down Expand Up @@ -392,6 +475,27 @@ pub extern "C" fn store() {
);
}

/// Store new contract. Non serialized argument is the contract.
/// Returns base58 of the hash of the contract.
#[no_mangle]
pub extern "C" fn upgrade_factory() {
env::setup_panic_hook();
let contract: SputnikDAOFactory = env::state_read().expect("Contract is not initialized");
contract.assert_owner();

let current_id = env::current_account_id();

let input = env::input().expect("ERR_NO_INPUT");

// Create a promise toward given account.
let promise_id = env::promise_batch_create(&current_id);

// Deploy the contract code.
env::promise_batch_action_deploy_contract(promise_id, &input);

env::promise_return(promise_id);
}

#[cfg(test)]
mod tests {
use near_sdk::test_utils::test_env::{alice, bob, carol};
Expand Down

0 comments on commit 90683cd

Please sign in to comment.