changed 4 years ago
Linked with GitHub

Sputnik v2 Smart Contract Documentation

DenysK is providing raw commands for the following actions:

TODO:

  • Creating a DAO with the Factory Contract
  • Contract methods
  • Arbitrary function calls to other contracts (Token Factory).
  • Staking Contract and adopting token
  • Assigning none-council roles and permissions
  • Policy changes
  • Bounties

Useful References

Deploy a new DAO with Sputnik v2 Factory

  1. Clone the repo

git clone https://github.com/near-daos/sputnik-dao-contract && cd sputnik-dao-contract/sputnikdao-factory2

  1. Build the contract and deploy

./build.sh

export NEAR_ENV=testnet && near deploy sputnik.testnet --wasmFile res/sputnikdao_factory2.wasm --accountId sputnik.testnet

  1. Change dao.testnet to your own NEAR account

export CONTRACT_ID="sputnik.testnet"

  1. Initialise the factory

near call $CONTRACT_ID new --accountId $CONTRACT_ID

  1. Create a new DAO with default parameters / call factory create method to create a new DAO, can be executed by any NEAR account.

    a. create base64 encoded string of arguments to provide to Sputnik DAO contract call:

    ​​​​ARGS=`echo '{"config": {"name": "genesis", "purpose": "Genesis DAO", "metadata":""}, "policy": ["council_member_1.testnet","council_member_2.testnet"]}' | base64`
    

    b. create DAO

    near call $CONTRACT_ID create "{\"name\": \"genesis\", \"args\": \"ARGS\"}" --accountId your_account.testnet --amount 5 --gas 150000000000000

attach minimum 150000000000000 of gas a new DAO will be created with following defaults:

{
  name: 'genesis',
  purpose: 'Genesis DAO',
  metadata: ''
}
{
  roles: [
    {
      name: 'all',
      kind: 'Everyone',
      permissions: [ '*:AddProposal' ],
      vote_policy: {}
    },
    {
      name: 'council',
      kind: { Group: [ 'council_member_1.testnet', 'council_member_2.testnet' ] },
      permissions: [ '*:*' ],
      vote_policy: {}
    }
  ],
  default_vote_policy: { weight_kind: 'RoleWeight', threshold: [ 1, 2 ] },
  proposal_bond: '1000000000000000000000000',
  proposal_period: '604800000000000',
  bounty_bond: '1000000000000000000000000',
  bounty_forgiveness_period: '86400000000000'
}

Add proposal, type A - Council member

near call genesis.sputnik.testnet add_proposal '{"proposal": {"description": "Add New Council", "kind": {"AddMemberToRole": {"member_id": "council_member_3.testnet", "role": "council"}}}}' --accountId proposer.testnet --amount 1

record ID returned from this call.

Approve proposal by council member

near call genesis.sputnik.testnet act_proposal '{"id": ID_from_previous_call, "action": "VoteApprove"}' --accountId council_member_1.testnet

Call arbitrary functions on other contracts

Example to create a FT from the DAO

Token factory

  1. Build token factory

    • Clone token factory contract git clone https://github.com/evgenykuzyakov/token-factory or git clone https://github.com/near-examples/token-factory
    • Build from sourcecd token-factory/contracts && ./build.sh
    • Deploy to NEAR account of your choice near deploy your_token_factory.testnet --wasmFile factory/res/token_factory.wasm --accountId your_token_factory.testnet
  2. Or use exiting one: testnet: tokenfactory.testnet, mainnet: tkn.near

  3. Prepare token arguments and metadata

`ARGS=`echo '{"args": {"owner_id": "your_account.testnet", "total_supply": "1000000000000000000000", "metadata": {"spec": "ft-1.0.0", "name": "FToken", "symbol": "FT", "icon": "", "decimals": 18}}}' | base64`

token specification:

  • total_supply - the total number of tokens in circulation. Example: The 1000 tokens is 1000 * 10^18 or as a number is 1000000000000000000000, reference
  • decimals - used in frontends to show the proper significant digits of a token, the precision ("decimals" in this metadata standard), reference: https://nomicon.io/Standards/FungibleToken/Metadata.html
  • icon - a small image associated with this token. Must be a data URL, to help consumers display it quickly while protecting user data.
  • name - the human-readable name of the token.
  • symbol - the abbreviation, like wETH or AMPL.

please refer to nomicon.io/Standards/FungibleToken/ for more details

  1. Create proposal for the new token near call your_dao.testnet add_proposal "{\"proposal\": {\"description\": \"Create a new token\", \"kind\": {\"FunctionCall\": {\"receiver_id\": \"your_token_factory.testnet\", \"actions\": [{\"method_name\": \"create_token\", \"args\": \"ARGS\", \"deposit\": \"3000000000000000000000000\", \"gas\": \"20000000000000\"}]}}}}" --accountId proposer_account.testnet --amount 5

    • deposit is in yoktoNEAR and required to store the token, normally between 2.6-3 NEAR
    • amount - is to cover deposit
    • the call will return proposal ID, record it for the next step
  2. Act on proposal near call genesis.sputnik.testnet act_proposal '{"id": ID_from_previous_step, "action": "VoteApprove"}' --accountId council_member_account.testnet --gas 250000000000000

Please note, that successful execution of #5 does not mean successful token creating, you have to check the expolorer by using URL provided in result of #5 and check if token was successfully created.

Adopting a voting token

Setup staking contract

In order to setup staking contract you need to deploy it on new NEAR account. We choose the option to utilise generic-factory to achive this, this approach can be used in the frontend

Please review factory here: https://github.com/near/core-contracts/tree/generic-factory/generic-factory

We have deployed the factory on the testnet and will be using this account in examples: generic.testnet, below the deployment code:

#!/bin/bash
set -e

REPL=$(cat <<-END
const accountId = "caller_account.testnet";
const contractName = "generic.testnet";
const fs = require('fs');
const account = await near.account(accountId);
const code = fs.readFileSync("sputnik-dao-contract/sputnik-staking/res/sputnik_staking.wasm");
account.signAndSendTransaction(
    contractName,
    [
        nearAPI.transactions.functionCall("store", code, 20000000000000, "10000000000000000000000000"),
    ]);
END
)
echo $REPL | near repl
  1. Call factory to deploy the Sputnik Staking contract

Please note, the result of above deployment returned the stored contract hash, which is required for deploying staking contract 4ThdGjTKbBTad45CyePPAiZmWJEpEoFwViFusy4cpEmA

near call generic.testnet create '{"name": "sputnik-staking","hash": "4ThdGjTKbBTad45CyePPAiZmWJEpEoFwViFusy4cpEmA","Cg== ": "","access_keys": ["<FULL_ACCESS_KEY>"]}' --accountId your_account.testnet --amount 5

it will create an account sputnik-staking.generic.testnet, send 5 NEAR for storage.

result:

  1. Initialize Sputnik Staking contract

Initialize with the token you want to vote with token_id and DAO that will be voting in owner_id. And unstake_period during which the token can’t be moved. Usually should match DAOs proposal period to avoid double voting.

Any token can be adopted, in this example we use a token created in the testnet version of tkn.farm

near call sputnik-staking.generic.testnet new '{"token_id": "<token created before>","owner_id": "<dao created before>", "unstake_period": "604800000000000"}' --accountId solo.testnet

  1. Register the account in the token contract by calling storage_deposit function

Register a user account who will pay for storage staking. This is an issue that needs to be addressed by any multi-user contract that allocates storage for the user on the blockchain. Storage staking costs are the most expensive costs to consider for the contract on NEAR.

near call bst.tokenfactory.testnet storage_deposit '{"account_id": "sputnik-staking.generic.testnet"}' --accountId solo.testnet --amount 1, result

{ total: '1250000000000000000000', available: '0' }

Adopt the staking contract

  1. Make a proposal to adopt the staking contract to vote for the DAO near call private4.sputnikv2.testnet add_proposal '{"proposal": {"description": "Adopt BST token for voting", "kind": {"SetStakingContract": {"staking_id": "sputnik-staking.generic.testnet"}}}}' --accountId solo.testnet --amount 1

  2. Approve proposal usual way

This staking contract is now added to the DAO
Please note! each DAO must have its own staking contract!!!

  1. Resever storage for the token owner account on staking contract
    near call sputnik-staking.generic.testnet storage_deposit '' --accountId solo.testnet --amount 0.003

  2. Transfer token to the staking contract
    near call bst.tokenfactory.testnet ft_transfer '{"receiver_id": "sputnik-staking.generic.testnet", "amount": "9000000000000000000000", "msg": ""}' --accountId starlink.testnet --amount 0.000000000000000000000001

Due to the nature of function-call permission access keys on NEAR protocol, the method that requires an attached deposit can't be called by the restricted access key. If the token contract requires an attached deposit of at least 1 yoctoNEAR on transfer methods, then the function-call restricted access key will not be able to call them without going through the wallet confirmation. This prevents some attacks like fishing through an authorization to a token contract.

  1. Delegate amount transferred to the staking contract
    near call putnik-staking.generic.testnet delegate '{"account_id": "starlink.testnet", "amount": "9000000000000000000000"}' --accountId starlink.testnet

Check the work

  1. View staking contract total supply
    near view private4.sputnikv2.testnet delegation_total_supply ''

  2. View user delegation
    near call private6-sputnik-staking.generic.testnet get_user '{"account_id": "sstarlink.testnet"}' --accountId starlink.testnet

example of above call results

{
  storage_used: 176,
  near_amount: '3000000000000000000000',
  vote_amount: '9000000000000000000000',
  next_action_timestamp: '0',
  delegated_amounts: [ [ 'starlink.testnet', '4000000000000000000000' ] ]
}

Changing Policy

Add proposal to change policy

In order to change policy you need to create a proposal ChangePolicy type with a whole policy JSON. For better visualisation we will use near repl in this example

  1. Create an empty file and make it executable touch change-policy.sh && chmod +x change-policy.sh
  2. Add following to the file: vi change-policy.sh
#!/bin/bash
set -e

REPL=$(cat <<-END
const accountId = "solo.testnet";
const contractName = "private4.sputnikv2.testnet";
const account = await near.account(accountId);
const args = {"proposal": {"description": "Change Policy", "kind": {"ChangePolicy": {
  "policy": {
  "roles": [
    {
      "name": "all",
      "kind": "Everyone",
      "permissions": [
      "*:AddProposal",
      "*:Finalize"
      ],
      "vote_policy": {}
    },
    {
      "name": "council",
      "kind": { "Group": [ "solo.testnet" ] },
      "permissions": [
        "*:Finalize",
        "*:AddProposal",
        "*:VoteApprove",
        "*:VoteReject",
        "*:VoteRemove"
      ],
      "vote_policy": {}
    },
    {
      "name": "influencers",
      "kind": { "Group": [] },
      "permissions": [
        "*:Finalize",
        "*:VoteApprove",
        "*:VoteReject",
        "*:MoveToHub"
      ],
      "vote_policy": {}
    }
  ],
  "default_vote_policy": { "weight_kind": "RoleWeight", "quorum": "0", "threshold": [ 1, 2 ] },
  "proposal_bond": "1000000000000000000000000",
  "proposal_period": "604800000000000",
  "bounty_bond": "1000000000000000000000000",
  "bounty_forgiveness_period": "86400000000000"
}}}}};

account.signAndSendTransaction(
    contractName,
    [
        nearAPI.transactions.functionCall("add_proposal", args, 20000000000000, "10000000000000000000000000"),
    ]);
END)

echo $REPL | near repl

in this example we will add a new role: influencers and later add memeber to that role. We assign it 4 permission types: Finalize, VoteApprove, VoteReject, MoveToHub
We also add finallise to the Everyone group

To check any DAO current policy, run near view <dao account> get_policy

The default DAO policy is:

View call: genesis.sputnikv2.testnet.get_policy()
{
  roles: [
    {
      name: 'all',
      kind: 'Everyone',
      permissions: [ '*:AddProposal' ],
      vote_policy: {}
    },
    {
      name: 'council',
      kind: { Group: [ 'testmewell.testnet', 'solo.testnet' ] },
      permissions: [
        '*:Finalize',
        '*:AddProposal',
        '*:VoteApprove',
        '*:VoteReject',
        '*:VoteRemove'
      ],
      vote_policy: {}
    }
  ],
  default_vote_policy: { weight_kind: 'RoleWeight', quorum: '0', threshold: [ 1, 2 ] },
  proposal_bond: '1000000000000000000000000',
  proposal_period: '604800000000000',
  bounty_bond: '1000000000000000000000000',
  bounty_forgiveness_period: '86400000000000'
}
  1. Execute file from console/terminal: ./change-policy.sh

  2. Approve proposal: near call private4.sputnikv2.testnet act_proposal '{"id": XX, "action": "VoteApprove"}' --accountId solo.testnet --gas 300000000000000

  3. And check the result: near view private4.sputnikv2.testnet get_policy ''

View call: private4.sputnikv2.testnet.get_policy()
{
  roles: [
    {
      name: 'all',
      kind: 'Everyone',
      permissions: [ '*:AddProposal', '*:Finalize' ],
      vote_policy: {}
    },
    {
      name: 'council',
      kind: { Group: [ 'solo.testnet' ] },
      permissions: [
        '*:Finalize',
        '*:AddProposal',
        '*:VoteApprove',
        '*:VoteReject',
        '*:VoteRemove'
      ],
      vote_policy: {}
    },
    {
      name: 'influencers',
      kind: { Group: [] },
      permissions: [ '*:VoteReject', '*:MoveToHub', '*:VoteApprove', '*:Finalize' ],
      vote_policy: {}
    }
  ],
  default_vote_policy: { weight_kind: 'RoleWeight', quorum: '0', threshold: [ 1, 2 ] },
  proposal_bond: '1000000000000000000000000',
  proposal_period: '604800000000000',
  bounty_bond: '1000000000000000000000000',
  bounty_forgiveness_period: '86400000000000'
}

We have just added a new member role: influencers and amended role Everyone to allow everyone to Finalise

Add new member to new role

Now when we have a new Role influencers, we can add a new member to it:

  1. Create a proposal near call private4.sputnikv2.testnet add_proposal '{"proposal": {"description": "Add New Influencer", "kind": {"AddMemberToRole": {"member_id": "starlink.testnet", "role": "influencers"}}}}' --accountId solo.testnet --amount 1
  2. Approve: near call private4.sputnikv2.testnet act_proposal '{"id": XX, "action": "VoteApprove"}' --accountId solo.testnet --gas 300000000000000
  3. Check new member is susseccfully added: near view private4.sputnikv2.testnet get_policy '
    {
      name: 'influencers',
      kind: { Group: [ 'starlink.testnet' ] },
      permissions: [ '*:VoteReject', '*:MoveToHub', '*:VoteApprove', '*:Finalize' ],
      vote_policy: {}
    }
Select a repo