From 04c24adcbdfa4d07b188bc148e4494aab3454d36 Mon Sep 17 00:00:00 2001 From: Linguists <95207870+linguists@users.noreply.github.com> Date: Fri, 18 Apr 2025 15:03:24 +0800 Subject: [PATCH 1/5] chore: v1.6.1 release --- Cargo.lock | 2 +- contracts/linear/Cargo.toml | 2 +- contracts/linear/src/upgrade.rs | 22 ++-------------------- 3 files changed, 4 insertions(+), 22 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index f7dc074c..54d90eb9 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -530,7 +530,7 @@ checksum = "8521a1b57e76b1ec69af7599e75e38e7b7fad6610f037db8c79b127201b5d119" [[package]] name = "linear" -version = "1.6.0" +version = "1.6.1" dependencies = [ "near-contract-standards", "near-sdk 4.0.0-pre.7", diff --git a/contracts/linear/Cargo.toml b/contracts/linear/Cargo.toml index 5c8d0fd2..82f4b4cf 100644 --- a/contracts/linear/Cargo.toml +++ b/contracts/linear/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "linear" -version = "1.6.0" +version = "1.6.1" authors = ["linguists", "dongcool"] edition = "2018" publish = false diff --git a/contracts/linear/src/upgrade.rs b/contracts/linear/src/upgrade.rs index f9f3ff4d..0cc831a9 100644 --- a/contracts/linear/src/upgrade.rs +++ b/contracts/linear/src/upgrade.rs @@ -1,5 +1,4 @@ // use crate::legacy::*; -use self::legacy::ContractV1_3_0; use crate::*; #[near_bindgen] @@ -12,25 +11,8 @@ impl LiquidStakingContract { #[init(ignore_state)] #[private] pub fn migrate() -> Self { - let contract: ContractV1_3_0 = env::state_read().expect("ERR_NOT_INITIALIZED"); - Self { - owner_id: contract.owner_id, - managers: contract.managers, - treasury_id: contract.treasury_id, - total_share_amount: contract.total_share_amount, - total_staked_near_amount: contract.total_staked_near_amount, - accounts: contract.accounts, - paused: contract.paused, - account_storage_usage: contract.account_storage_usage, - beneficiaries: contract.beneficiaries, - validator_pool: contract.validator_pool, - whitelist_account_id: contract.whitelist_account_id, - epoch_requested_stake_amount: contract.epoch_requested_stake_amount, - epoch_requested_unstake_amount: contract.epoch_requested_unstake_amount, - stake_amount_to_settle: contract.stake_amount_to_settle, - unstake_amount_to_settle: contract.unstake_amount_to_settle, - last_settlement_epoch: contract.last_settlement_epoch, - } + let contract: LiquidStakingContract = env::state_read().expect("ERR_NOT_INITIALIZED"); + contract } } From a3f7b5dc0428f751cb4c81b751d03c3449a63eeb Mon Sep 17 00:00:00 2001 From: Linguists <95207870+linguists@users.noreply.github.com> Date: Mon, 21 Apr 2025 09:59:28 +0800 Subject: [PATCH 2/5] chore: fix propose upgrade command --- bin/commands/common.js | 16 +++++++++++++++- bin/commands/propose-upgrade.js | 29 +++++++++++++++++------------ 2 files changed, 32 insertions(+), 13 deletions(-) diff --git a/bin/commands/common.js b/bin/commands/common.js index 78c9a2e7..a968a7e3 100644 --- a/bin/commands/common.js +++ b/bin/commands/common.js @@ -1,5 +1,19 @@ -module.exports.networkOption = { +const prompts = require('prompts'); + +exports.networkOption = { describe: 'network ID', default: 'testnet', choices: ['testnet', 'mainnet', 'localnet'] }; + +exports.doubleCheck = async () => { + const res = await prompts({ + type: 'toggle', + name: 'value', + message: 'Confirm?', + initial: true, + active: 'yes', + inactive: 'no' + }); + if (!res.value) process.exit(1); +} diff --git a/bin/commands/propose-upgrade.js b/bin/commands/propose-upgrade.js index bf4bb582..4d894b4d 100644 --- a/bin/commands/propose-upgrade.js +++ b/bin/commands/propose-upgrade.js @@ -3,7 +3,8 @@ const sha256 = require('sha256'); const { readFileSync, appendFileSync, existsSync } = require("fs"); const { NEAR, Gas } = require("near-units"); const { init } = require("../near"); -const { networkOption } = require("./common"); +const nearAPI = require('near-api-js'); +const { networkOption, doubleCheck } = require("./common"); exports.command = 'propose-upgrade
'; exports.desc = 'Propose an upgrade in DAO'; @@ -72,13 +73,13 @@ exports.handler = async function (argv) { console.error(`Old blob with ${lastHash} doesn't exist. The blob might have been removed. Continue?`); await doubleCheck(); } else { - console.log(`Remove blob with hash ${lastHash}. Are you sure?`); + console.log(`Remove outdated blob with hash ${lastHash}. Are you sure?`); await doubleCheck(); await signer.functionCall({ contractId: dao, methodName: 'remove_blob', args: { - hash, + hash: lastHash, }, }); console.log(`Removed blob with hash ${lastHash}`); @@ -96,15 +97,19 @@ exports.handler = async function (argv) { // store new blob console.log(`Store blob with hash ${codeHash}. Are you sure?`); await doubleCheck(); - const outcome = await signer.functionCall({ - contractId: dao, - methodName: 'store_blob', - args: { - code, - }, - gas: Gas.parse('100 Tgas'), - attachedDeposit: deposit, - }); + const outcome = await signer.signAndSendTransaction( + { + receiverId: dao, + actions: [ + nearAPI.transactions.functionCall( + 'store_blob', + code, + Gas.parse('100 Tgas'), + deposit + ) + ] + } + ); const hash = parseHashReturnValue(outcome); console.log(`Stored blob with hash ${hash}`); } From 87d482f9438cb2e3ab5a6c71bfeb1df0af6791b3 Mon Sep 17 00:00:00 2001 From: Linguists <95207870+linguists@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:45:50 +0800 Subject: [PATCH 3/5] chore: store dao contract code --- bin/commands/common.js | 18 +++++++ bin/commands/propose-upgrade.js | 20 +------ bin/commands/store-dao-contract.js | 85 ++++++++++++++++++++++++++++++ 3 files changed, 104 insertions(+), 19 deletions(-) create mode 100644 bin/commands/store-dao-contract.js diff --git a/bin/commands/common.js b/bin/commands/common.js index a968a7e3..2ebfa20c 100644 --- a/bin/commands/common.js +++ b/bin/commands/common.js @@ -1,4 +1,6 @@ const prompts = require('prompts'); +const base58 = require('bs58'); +const sha256 = require('sha256'); exports.networkOption = { describe: 'network ID', @@ -17,3 +19,19 @@ exports.doubleCheck = async () => { }); if (!res.value) process.exit(1); } + +exports.parseHashReturnValue = (outcome) => { + const status = outcome.status; + const data = status.SuccessValue; + if (!data) { + throw new Error('bad return value'); + } + + const buff = Buffer.from(data, 'base64'); + return buff.toString('ascii').replaceAll('"', ""); +} + +exports.getBase58CodeHash = (code) => { + const hash = Buffer.from(sha256(code), 'hex'); + return base58.encode(hash); +} \ No newline at end of file diff --git a/bin/commands/propose-upgrade.js b/bin/commands/propose-upgrade.js index 4d894b4d..39621daf 100644 --- a/bin/commands/propose-upgrade.js +++ b/bin/commands/propose-upgrade.js @@ -1,10 +1,8 @@ -const base58 = require('bs58'); -const sha256 = require('sha256'); const { readFileSync, appendFileSync, existsSync } = require("fs"); const { NEAR, Gas } = require("near-units"); const { init } = require("../near"); const nearAPI = require('near-api-js'); -const { networkOption, doubleCheck } = require("./common"); +const { networkOption, doubleCheck, parseHashReturnValue, getBase58CodeHash } = require("./common"); exports.command = 'propose-upgrade
'; exports.desc = 'Propose an upgrade in DAO'; @@ -141,19 +139,3 @@ exports.handler = async function (argv) { console.log('proposed!'); } - -function parseHashReturnValue(outcome) { - const status = outcome.status; - const data = status.SuccessValue; - if (!data) { - throw new Error('bad return value'); - } - - const buff = Buffer.from(data, 'base64'); - return buff.toString('ascii').replaceAll('"', ""); -} - -function getBase58CodeHash(code) { - const hash = Buffer.from(sha256(code), 'hex'); - return base58.encode(hash); -} diff --git a/bin/commands/store-dao-contract.js b/bin/commands/store-dao-contract.js new file mode 100644 index 00000000..8f47267c --- /dev/null +++ b/bin/commands/store-dao-contract.js @@ -0,0 +1,85 @@ +const { writeFileSync } = require("fs"); +const { NEAR, Gas } = require("near-units"); +const { init } = require("../near"); +const nearAPI = require('near-api-js'); +const { networkOption, doubleCheck, parseHashReturnValue, getBase58CodeHash } = require("./common"); + +exports.command = 'store-dao-contract '; +exports.desc = 'Store DAO contract code from DAO factory'; +exports.builder = yargs => { + yargs + .positional('dao', { + describe: 'DAO contract address to deploy to', + type: 'string' + }) + .option('network', networkOption) + .option('wasm', { + describe: 'DAO contract wasm file path', + default: 'res/sputnikdao.wasm' + }) + .option('signer', { + describe: 'signer account ID to call new' + }) + .option('hash', { + describe: 'DAO contract code hash to store' + }) + .demandOption(['signer', 'dao', 'hash']) +} + +exports.handler = async function (argv) { + const { dao, hash, network } = argv; + const factory = dao.split('.').slice(1).join('.'); + console.log(`Fetch DAO contract code from factory ${factory} with code hash ${hash} ...`); + + const near = await init(network); + const signer = await near.account(argv.signer); + const contract = await near.account(dao); + + const code = await signer.viewFunction(factory, 'get_code', { code_hash: hash }, { parse: (code) => code }); + writeFileSync(argv.wasm, code); + const codeHash = getBase58CodeHash(code); + if(codeHash !== hash) { + console.error(`Fetched code hash ${codeHash} is not the same as ${hash}`); + return; + } + + const deposit = (BigInt(code.length + 32) * 10n ** 19n).toString() + + console.log(`Store DAO contract code with code hash ${codeHash} to DAO ${dao}`); + console.log(`- Code hash: ${codeHash}`); + console.log(`- Storage cost: ${NEAR.from(deposit).toHuman()}`); + console.log(`- DAO: ${dao}`); + + const deployedCodeHash = (await contract.state()).code_hash; + if (codeHash === deployedCodeHash) { + console.log( + "Contract's code hash is the same as the wasm file. There's no need to store the same code again.", + ); + return; + } + + // check if the blob already exists + const found = await signer.viewFunction(dao, 'has_blob', { hash: codeHash }); + if (found) { + console.error(`The blob with ${codeHash} already exists. No need to store the same blob.`); + } else { + // store new blob + console.log(`Store blob with hash ${codeHash}. Are you sure?`); + await doubleCheck(); + const outcome = await signer.signAndSendTransaction( + { + receiverId: dao, + actions: [ + nearAPI.transactions.functionCall( + 'store_blob', + code, + Gas.parse('100 Tgas'), + deposit + ) + ] + } + ); + const hash = parseHashReturnValue(outcome); + console.log(`Stored blob with hash ${hash}`); + } +} From 2ac510929852f47df3160b8988d1215e7f6daa17 Mon Sep 17 00:00:00 2001 From: Linguists <95207870+linguists@users.noreply.github.com> Date: Mon, 21 Apr 2025 17:51:12 +0800 Subject: [PATCH 4/5] chore: update commands --- bin/commands/common.js | 2 +- bin/commands/propose-upgrade.js | 2 +- bin/commands/store-dao-contract.js | 9 +++++---- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/bin/commands/common.js b/bin/commands/common.js index 2ebfa20c..cefd0bf6 100644 --- a/bin/commands/common.js +++ b/bin/commands/common.js @@ -34,4 +34,4 @@ exports.parseHashReturnValue = (outcome) => { exports.getBase58CodeHash = (code) => { const hash = Buffer.from(sha256(code), 'hex'); return base58.encode(hash); -} \ No newline at end of file +} diff --git a/bin/commands/propose-upgrade.js b/bin/commands/propose-upgrade.js index 39621daf..ceb8dee1 100644 --- a/bin/commands/propose-upgrade.js +++ b/bin/commands/propose-upgrade.js @@ -18,7 +18,7 @@ exports.builder = yargs => { default: 'res/linear.wasm' }) .option('signer', { - describe: 'signer account ID to call new' + describe: 'signer account ID' }) .option('dao', { describe: 'DAO account Id' diff --git a/bin/commands/store-dao-contract.js b/bin/commands/store-dao-contract.js index 8f47267c..e050d570 100644 --- a/bin/commands/store-dao-contract.js +++ b/bin/commands/store-dao-contract.js @@ -9,7 +9,7 @@ exports.desc = 'Store DAO contract code from DAO factory'; exports.builder = yargs => { yargs .positional('dao', { - describe: 'DAO contract address to deploy to', + describe: 'DAO contract address to store DAO contract code', type: 'string' }) .option('network', networkOption) @@ -18,7 +18,7 @@ exports.builder = yargs => { default: 'res/sputnikdao.wasm' }) .option('signer', { - describe: 'signer account ID to call new' + describe: 'signer account ID' }) .option('hash', { describe: 'DAO contract code hash to store' @@ -28,6 +28,7 @@ exports.builder = yargs => { exports.handler = async function (argv) { const { dao, hash, network } = argv; + const factory = dao.split('.').slice(1).join('.'); console.log(`Fetch DAO contract code from factory ${factory} with code hash ${hash} ...`); @@ -39,13 +40,13 @@ exports.handler = async function (argv) { writeFileSync(argv.wasm, code); const codeHash = getBase58CodeHash(code); if(codeHash !== hash) { - console.error(`Fetched code hash ${codeHash} is not the same as ${hash}`); + console.error(`The fetched code hash ${codeHash} is not the same as the provided ${hash}`); return; } const deposit = (BigInt(code.length + 32) * 10n ** 19n).toString() - console.log(`Store DAO contract code with code hash ${codeHash} to DAO ${dao}`); + console.log(`Store DAO contract code with hash ${codeHash} to DAO ${dao}`); console.log(`- Code hash: ${codeHash}`); console.log(`- Storage cost: ${NEAR.from(deposit).toHuman()}`); console.log(`- DAO: ${dao}`); From 4f3d403238cff8a8f9c0e862dc0ddd6047af344e Mon Sep 17 00:00:00 2001 From: Linguists <95207870+linguists@users.noreply.github.com> Date: Tue, 6 May 2025 14:04:01 +0800 Subject: [PATCH 5/5] docs: fix readme node version --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index c24032bc..190551ef 100644 --- a/README.md +++ b/README.md @@ -36,7 +36,7 @@ The LiNEAR smart contracts have been audited by [BlockSec](https://www.blocksect We adopt unit tests and heavily used the [`workspace-js`](https://github.com/near/workspaces-js) test framework to test the major scenarios and workflow of the LiNEAR smart contract in the [Sandbox](https://docs.near.org/docs/develop/contracts/sandbox) environment. Lint with `rustfmt` and `clippy` is also required when making changes to contract. -- Install node v16 +- Install Node.js v20 - Run `npm i` to set up the environment - Run lint with `rustfmt` and `clippy`: `make lint` - Run all tests: `make test`