Cargo.toml
README.md
as-pect.config.js
asconfig.json
package.json
simulation
    Cargo.toml
    README.md
    src
        lib.rs
        meme.rs
        museum.rs
INIT MUSEUM
ADD CONTRIBUTOR
CREATE MEME
VERIFY MEME
src
    as-pect.d.ts
    as_types.d.ts
    meme
        README.md
        __tests__
            README.md
            index.unit.spec.ts
        asconfig.json
        assembly
            index.ts
            models.ts
    museum
        README.md
        __tests__
            README.md
            index.unit.spec.ts
        asconfig.json
        assembly
            index.ts
            models.ts
    tsconfig.json
    utils.ts

# Meme Contract

**NOTE**

If you try to call a method which requires a signature from a valid account, you will see this error:

```txt
"error": "wasm execution failed with error: FunctionCallError(HostError(ProhibitedInView ..."
```

This will happen anytime you try using `near view ...` when you should be using `near call ...`.  So it's important to pay close attention in the following examples as to which is being used, a `view` or a `call` (aka. "change") method.

----

## deployment

```sh
near dev-deploy ./build/release/meme.wasm
```

## initialization

`init(title: string, data: string, category: Category): void`

```sh
# anyone can initialize meme (so this must be done by the museum at deploy-time)
near call dev-1614603380541-7288163 init '{"title": "hello world", "data": "https://9gag.com/gag/ayMDG8Y", "category": 0}' --account_id dev-1614603380541-7288163 --amount 3
```

## view methods

`get_meme(): Meme`

```sh
# anyone can read meme metadata
near view dev-1614603380541-7288163 get_meme
```

```js
{
  creator: 'dev-1614603380541-7288163',
  created_at: '1614603702927464728',
  vote_score: 4,
  total_donations: '0',
  title: 'hello world',
  data: 'https://9gag.com/gag/ayMDG8Y',
  category: 0
}
```

`get_recent_votes(): Array<Vote>`

```sh
# anyone can request a list of recent votes
near view dev-1614603380541-7288163 get_recent_votes
```

```js
[
  {
    created_at: '1614603886399296553',
    value: 1,
    voter: 'dev-1614603380541-7288163'
  },
  {
    created_at: '1614603988616406809',
    value: 1,
    voter: 'sherif.testnet'
  },
  {
    created_at: '1614604214413823755',
    value: 2,
    voter: 'batch-dev-1614603380541-7288163'
  },
  [length]: 3
]
```

`get_vote_score(): i32`

```sh
near view dev-1614603380541-7288163 get_vote_score
```

```js
4
```

`get_donations_total(): u128`

```sh
near view dev-1614603380541-7288163 get_donations_total
```

```js
'5000000000000000000000000'
```

`get_recent_donations(): Array<Donation>`

```sh
near view dev-1614603380541-7288163 get_recent_donations
```

```js
[
  {
    amount: '5000000000000000000000000',
    donor: 'sherif.testnet',
    created_at: '1614604980292030188'
  },
  [length]: 1
]
```

## change methods

`vote(value: i8): void`

```sh
# user votes for meme
near call dev-1614603380541-7288163 vote '{"value": 1}' --account_id sherif.testnet
```

`batch_vote(value: i8, is_batch: bool = true): void`

```sh
# only the meme contract can call this method
near call dev-1614603380541-7288163 batch_vote '{"value": 2}' --account_id dev-1614603380541-7288163
```

`add_comment(text: string): void`

```sh
near call dev-1614603380541-7288163 add_comment '{"text":"i love this meme"}' --account_id sherif.testnet
```

`get_recent_comments(): Array<Comment>`

```sh
near view dev-1614603380541-7288163 get_recent_comments
```

```js
[
  {
    created_at: '1614604543670811624',
    author: 'sherif.testnet',
    text: 'i love this meme'
  },
  [length]: 1
]
```

`donate(): void`

```sh
near call dev-1614603380541-7288163 donate --account_id sherif.testnet --amount 5
```

`release_donations(account: AccountId): void`

```sh
near call dev-1614603380541-7288163 release_donations '{"account":"sherif.testnet"}' --account_id dev-1614603380541-728816
```

This method automatically calls `on_donations_released` which logs *"Donations were released"*
# Museum Contract

**NOTE**

If you try to call a method which requires a signature from a valid account, you will see this error:

```txt
"error": "wasm execution failed with error: FunctionCallError(HostError(ProhibitedInView ..."
```

This will happen anytime you try using `near view ...` when you should be using `near call ...`.  So it's important to pay close attention in the following examples as to which is being used, a `view` or a `call` (aka. "change") method.

----

## environment

```sh
# contract source code
export WASM_FILE=./build/release/museum.wasm

# system accounts
export CONTRACT_ACCOUNT=# NEAR account where the contract will live
export MASTER_ACCOUNT=$CONTRACT_ACCOUNT # can be any NEAR account that controls CONTRACT_ACCOUNT

# user accounts
export OWNER_ACCOUNT=# NEAR account that will control the museum
export CONTRIBUTOR_ACCOUNT=# NEAR account that will contribute memes

# configuration metadata
export MUSEUM_NAME="The Meme Museum" # a name for the museum itself, just metadata
export ATTACHED_TOKENS=3 # minimum tokens to attach to the museum initialization method (for storage)
export ATTACHED_GAS="300000000000000" # maximum allowable attached gas is 300Tgas (300 "teragas", 300 x 10^12)
```

## deployment

**Approach #1**: using `near dev-deploy` for developer convenience

This contract can be deployed to a temporary development account that's automatically generated:

```sh
near dev-deploy $WASM_FILE
export CONTRACT_ACCOUNT=# the account that appears as a result of running this command
```

The result of executing the above command will be a temporary `dev-###-###` account and related `FullAccess` keys with the contract deployed (to this same account).

**Approach #2**: using `near deploy` for more control

Alternatively, the contract can be deployed to a specific account for which a `FullAccess` key is available.  This account must be created and funded first.

```sh
near create-account $CONTRACT_ACCOUNT --masterAccount $MASTER_ACCOUNT
near deploy $WASM_FILE
```

This manual deployment method is the only way to deploy a contract to a specific account.  It's important to consider initializing the contract in the same step.  This is clarified below in the "initialization" section.

## initialization

`init(name: string, owners: AccountId[]): void`

**Approach #1**: initialize after `dev-deploy`

After `near dev-deploy` we can initialize the contract

```sh
# initialization method arguments
# '{"name":"The Meme Museum", "owners": ["<owner-account-id>"]}'
export INIT_METHOD_ARGS="'{\"name\":\"$MUSEUM_NAME\", \"owners\": [\"$OWNER_ACCOUNT\"]}'"

# initialize contract
near call $CONTRACT_ACCOUNT init $INIT_METHOD_ARGS --account_id $CONTRACT_ACCOUNT --amount $ATTACHED_TOKENS
```

**Approach #2**: deploy and initialize in a single step

Or we can initialize at the same time as deploying.  This is particularly useful for production deployments where an adversarial validator may try to front-run your contract initialization unless you bundle the `FunctionCall` action to `init()` as part of the transaction to `DeployContract`.

```sh
# initialization method arguments
# '{"name":"The Meme Museum", "owners": ["<owner-account-id>"]}'
export INIT_METHOD_ARGS="'{\"name\":\"$MUSEUM_NAME\", \"owners\": [\"$OWNER_ACCOUNT\"]}'"

# deploy AND initialize contract in a single step
near deploy $CONTRACT_ACCOUNT $WASM_FILE --initFunction init --initArgs $INIT_METHOD_ARGS --account_id $CONTRACT_ACCOUNT --initDeposit $ATTACHED_TOKENS
```

## view methods

`get_museum(): Museum`

```sh
near view $CONTRACT_ACCOUNT get_museum
```

```js
{ created_at: '1614636541756865886', name: 'The Meme Museum' }
```

`get_owner_list(): AccountId[]`

```sh
near view $CONTRACT_ACCOUNT get_owner_list
```

```js
[ '<owner-account-id>', [length]: 1 ]
```

`get_meme_list(): AccountId[]`

```sh
near view $CONTRACT_ACCOUNT get_meme_list
```

```js
[ 'usain', [length]: 1 ]
```

`get_meme_count(): u32`

```sh
near view $CONTRACT_ACCOUNT get_meme_count
```

```js
1
```

## change methods

### contributor methods

`add_myself_as_contributor(): void`

```sh
near call $CONTRACT_ACCOUNT add_myself_as_contributor --account_id $CONTRIBUTOR_ACCOUNT
```

`remove_myself_as_contributor(): void`

```sh
near call $CONTRACT_ACCOUNT remove_myself_as_contributor --account_id $CONTRIBUTOR_ACCOUNT
```

`add_meme(meme: AccountId, title: string, data: string, category: Category): void`

```sh
near call $CONTRACT_ACCOUNT add_meme '{"meme":"usain", "title": "usain refrain","data":"https://9gag.com/gag/ayMDG8Y", "category": 0 }' --account_id $CONTRIBUTOR_ACCOUNT --amount $ATTACHED_TOKENS --gas $ATTACHED_GAS
```

`on_meme_created(meme: AccountId): void`

This method is called automatically by `add_meme()` as a confirmation of meme account creation.

### owner methods

`add_contributor(account: AccountId): void`

```sh
# initialization method arguments
# '{"account":"<contributor-account-id>"}'
export METHOD_ARGS="'{\"account\":\"$CONTRIBUTOR_ACCOUNT\"}'"
near call $CONTRACT_ACCOUNT add_contributor $METHOD_ARGS --account_id $OWNER_ACCOUNT
```

`remove_contributor(account: AccountId): void`

```sh
near call $CONTRACT_ACCOUNT remove_contributor '{"account":"<contributor-account-id>"}' --account_id $OWNER_ACCOUNT
```

`add_owner(account: AccountId): void`

```sh
near call $CONTRACT_ACCOUNT add_owner '{"account":"<new-owner-account-id>"}' --account_id $OWNER_ACCOUNT
```

`remove_owner(account: AccountId): void`

```sh
near call $CONTRACT_ACCOUNT remove_owner '{"account":"<some-owner-account-id>"}' --account_id $OWNER_ACCOUNT
```

`remove_meme(meme: AccountId): void`

```sh
near call $CONTRACT_ACCOUNT remove_meme '{"meme":"usain"}' --account_id $OWNER_ACCOUNT
```

`on_meme_removed(meme: AccountId): void`

This method is called automatically by `remove_meme()` as a confirmation of meme account deletion.
# The Meme Museum

This repository includes contracts for NEAR Academy

## ⚠️ Warning

Any content produced by NEAR, or developer resources that NEAR provides, are for educational and inspiration purposes only.  NEAR does not encourage, induce or sanction the deployment of any such applications in violation of applicable laws or regulations.

## Usage

### Getting started

1. clone this repo to a local folder
2. run `yarn`
3. run `yarn test`

### Top-level `yarn` commands

- run `yarn test` to run all tests
  - (!) be sure to run `yarn build:release` at least once before:
    - run `yarn test:unit` to run only unit tests
    - run `yarn test:simulate` to run only simulation tests
- run `yarn build` to quickly verify build status
- run `yarn clean` to clean up build folder

### Other documentation

- **Meme** contract and test documentation
  - see `/src/meme/README` for Meme interface
  - see `/src/meme/__tests__/README` for Meme unit testing details

- **Museum** contract and test documentation
  - see `/src/museum/README` for Museum interface
  - see `/src/museum/__tests__/README` for Museum unit testing details

- simulation tests
  - see `/simulation/README` for simulation testing

## The file system

Please note that boilerplate project configuration files have been ommitted from the following lists for simplicity.

### Contracts and Unit Tests

```txt
src
├── meme                          <-- Meme contract
│   ├── README.md
│   ├── __tests__
│   │   ├── README.md
│   │   └── index.unit.spec.ts
│   └── assembly
│       ├── index.ts
│       └── models.ts
├── museum                        <-- Museum contract
│   ├── README.md
│   ├── __tests__
│   │   ├── README.md
│   │   └── index.unit.spec.ts
│   └── assembly
│       ├── index.ts
│       └── models.ts
└── utils.ts                      <-- shared contract code
```

### Simulation Tests

```txt
simulation                        <-- simulation tests
├── Cargo.toml
├── README.md
└── src
    ├── lib.rs
    ├── meme.rs
    └── museum.rs
```
# Simulation Tests

## Usage

`yarn test:simulate`

## File Structure

```txt
simulation
├── Cargo.toml      <-- Rust project config
├── README.md       <-- * you are here
└── src
    ├── lib.rs      <-- this is the business end of simulation
    ├── meme.rs     <-- type wrapper for Meme contract
    └── museum.rs   <-- type wrapper for Museum contract
```

## Orientation

The simulation environment requires that we

## Output

```txt
running 1 test
---------------------------------------
---- INIT MUSEUM ----------------------
---------------------------------------

ExecutionResult {
    outcome: ExecutionOutcome {
        logs: [
            "museum was created",
        ],
        receipt_ids: [],
        burnt_gas: 4354462070277,
        tokens_burnt: 0,
        status: SuccessValue(``),
    },
}

---------------------------------------
---- ADD CONTRIBUTOR ------------------
---------------------------------------

ExecutionResult {
    outcome: ExecutionOutcome {
        logs: [
            "contributor was added",
        ],
        receipt_ids: [],
        burnt_gas: 4884161460212,
        tokens_burnt: 0,
        status: SuccessValue(``),
    },
}

---------------------------------------
---- CREATE MEME ----------------------
---------------------------------------

[
    Some(
        ExecutionResult {
            outcome: ExecutionOutcome {
                logs: [],
                receipt_ids: [
                    `AmMRhhhYir4wNuUxhf8uCoKgnpv5nHQGvBqAeEWFL344`,
                ],
                burnt_gas: 2428142357466,
                tokens_burnt: 0,
                status: SuccessReceiptId(AmMRhhhYir4wNuUxhf8uCoKgnpv5nHQGvBqAeEWFL344),
            },
        },
    ),
    Some(
        ExecutionResult {
            outcome: ExecutionOutcome {
                logs: [
                    "attempting to create meme",
                ],
                receipt_ids: [
                    `B2BBAoJYj3EFE3Co6PRmfkXopTD654gUj8H6ywSsQD9e`,
                    `4NWBWN9dWuwiwbkwnub9Rv134Ubu2eJmbkdk1bJzeFtr`,
                ],
                burnt_gas: 19963342520004,
                tokens_burnt: 0,
                status: SuccessValue(``),
            },
        },
    ),
    Some(
        ExecutionResult {
            outcome: ExecutionOutcome {
                logs: [],
                receipt_ids: [],
                burnt_gas: 4033749130056,
                tokens_burnt: 0,
                status: SuccessValue(``),
            },
        },
    ),
    Some(
        ExecutionResult {
            outcome: ExecutionOutcome {
                logs: [
                    "Meme [ usain.museum ] successfully created",
                ],
                receipt_ids: [],
                burnt_gas: 4644097556149,
                tokens_burnt: 0,
                status: SuccessValue(``),
            },
        },
    ),
]

---------------------------------------
---- VERIFY MEME ----------------------
---------------------------------------

Object({
    "creator": String(
        "museum",
    ),
    "created_at": String(
        "17000000000",
    ),
    "vote_score": Number(
        0,
    ),
    "total_donations": String(
        "0",
    ),
    "title": String(
        "usain refrain",
    ),
    "data": String(
        "https://9gag.com/gag/ayMDG8Y",
    ),
    "category": Number(
        0,
    ),
})

test test::test_add_meme ... ok

test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

   Doc-tests simulation-near-academy-contracts

running 0 tests

test result: ok. 0 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out

✨  Done in 20.99s.

```
# Unit Tests for `Museum` Contract

## Usage

```sh
yarn test:unit -f museum
```

## Output

*Note: the tests marked with `Todo` must be verified using simulation tests because they involve cross-contract calls (which can not be verified using unit tests).*

```txt
[Describe]: museum initialization

 [Success]: ✔ creates a new museum with proper metadata
 [Success]: ✔ prevents double initialization
 [Success]: ✔ requires title not to be blank
 [Success]: ✔ requires a minimum balance

[Describe]: Museum self-service methods

 [Success]: ✔ returns a list of owners
 [Success]: ✔ returns a list of memes
 [Success]: ✔ returns a count of memes
 [Success]: ✔ allows users to add / remove themselves as contributors
    [Todo]: allows whitelisted contributors to create a meme

[Describe]: Museum owner methods

 [Success]: ✔ allows owners to whitelist a contributor
 [Success]: ✔ allows owners to remove a contributor
 [Success]: ✔ allows owners to add a new owner
 [Success]: ✔ allows owners to remove an owner
    [Todo]: allows owners to remove a meme

    [File]: src/museum/__tests__/index.unit.spec.ts
  [Groups]: 4 pass, 4 total
  [Result]: ✔ PASS
[Snapshot]: 0 total, 0 added, 0 removed, 0 different
 [Summary]: 12 pass,  0 fail, 12 total
    [Time]: 29.181ms

~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

  [Result]: ✔ PASS
   [Files]: 1 total
  [Groups]: 4 count, 4 pass
   [Tests]: 12 pass, 0 fail, 12 total
    [Time]: 8846.571ms
✨  Done in 9.51s.
```
# Unit Tests for `Meme` Contract

## Usage

```sh
yarn test:unit -f meme
```

## Output

*Note: the tests marked with `Todo` must be verified using simulation tests because they involve cross-contract calls (which can not be verified using unit tests).*

```txt
[Describe]: meme initialization

 [Success]: ✔ creates a new meme with proper metadata
 [Success]: ✔ prevents double initialization
 [Success]: ✔ requires title not to be blank
 [Success]: ✔ requires a minimum balance

[Describe]: meme voting

 [Success]: ✔ allows individuals to vote
 [Success]: ✔ prevents vote automation for individuals
 [Success]: ✔ prevents any user from voting more than once

[Describe]: meme captures votes

 [Success]: ✔ captures all votes
 [Success]: ✔ calculates a running vote score
 [Success]: ✔ returns a list of recent votes

 [Success]: ✔ allows groups to vote

[Describe]: meme comments

 [Success]: ✔ captures comments
 [Success]: ✔ rejects comments that are too long
 [Success]: ✔ captures multiple comments

[Describe]: meme donations

 [Success]: ✔ captures donations
[Describe]: captures donations

 [Success]: ✔ captures all donations
 [Success]: ✔ calculates a running donations total
 [Success]: ✔ returns a list of recent donations

    [Todo]: releases donations

    [File]: src/meme/__tests__/index.unit.spec.ts
  [Groups]: 7 pass, 7 total
  [Result]: ✔ PASS
[Snapshot]: 0 total, 0 added, 0 removed, 0 different
 [Summary]: 18 pass,  0 fail, 18 total
    [Time]: 46.469ms
```
