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

feat: upgrade factory from DAO #193

Open
wants to merge 4 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,5 @@ neardev

**/nodemodules
**/*.lock.json

.DS_Store
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(
agileurbanite marked this conversation as resolved.
Show resolved Hide resolved
&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
6 changes: 1 addition & 5 deletions sputnikdao2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -110,13 +110,9 @@ impl Contract {
/// This is NOOP implementation. KEEP IT if you haven't changed contract state.
/// If you have changed state, you need to implement migration from old state (keep the old struct with different name to deserialize it first).
/// After migrate goes live on MainNet, return this implementation for next updates.
#[private]
#[init(ignore_state)]
pub fn migrate() -> Self {
assert_eq!(
env::predecessor_account_id(),
env::current_account_id(),
"ERR_NOT_ALLOWED"
);
let this: Contract = env::state_read().expect("ERR_CONTRACT_IS_NOT_INITIALIZED");
this
}
Expand Down