Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Expand Down
34 changes: 33 additions & 1 deletion bin/commands/common.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,37 @@
module.exports.networkOption = {
const prompts = require('prompts');
const base58 = require('bs58');
const sha256 = require('sha256');

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);
}

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);
}
49 changes: 18 additions & 31 deletions bin/commands/propose-upgrade.js
Original file line number Diff line number Diff line change
@@ -1,9 +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 { networkOption } = require("./common");
const nearAPI = require('near-api-js');
const { networkOption, doubleCheck, parseHashReturnValue, getBase58CodeHash } = require("./common");

exports.command = 'propose-upgrade <address>';
exports.desc = 'Propose an upgrade in DAO';
Expand All @@ -19,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'
Expand Down Expand Up @@ -72,13 +71,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}`);
Expand All @@ -96,15 +95,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}`);
}
Expand Down Expand Up @@ -136,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);
}
86 changes: 86 additions & 0 deletions bin/commands/store-dao-contract.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
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 <dao>';
exports.desc = 'Store DAO contract code from DAO factory';
exports.builder = yargs => {
yargs
.positional('dao', {
describe: 'DAO contract address to store DAO contract code',
type: 'string'
})
.option('network', networkOption)
.option('wasm', {
describe: 'DAO contract wasm file path',
default: 'res/sputnikdao.wasm'
})
.option('signer', {
describe: 'signer account ID'
})
.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(`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 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}`);
}
}
2 changes: 1 addition & 1 deletion contracts/linear/Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "linear"
version = "1.6.0"
version = "1.6.1"
authors = ["linguists", "dongcool"]
edition = "2018"
publish = false
Expand Down
22 changes: 2 additions & 20 deletions contracts/linear/src/upgrade.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
// use crate::legacy::*;
use self::legacy::ContractV1_3_0;
use crate::*;

#[near_bindgen]
Expand All @@ -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
}
}

Expand Down
Loading