Skip to content

Commit

Permalink
Merge pull request #27 from aeternity/feat/contract
Browse files Browse the repository at this point in the history
feat: add contract project
  • Loading branch information
Kalovelo authored Jul 18, 2022
2 parents b897ebb + 441b62b commit 168ebfa
Show file tree
Hide file tree
Showing 15 changed files with 7,763 additions and 1 deletion.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
docker-compose.override.yml
node_modules/
.env.production
.env.production
Makefile
53 changes: 53 additions & 0 deletions contract/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
module.exports = {
env: {
browser : true,
commonjs : true,
es2021 : true,
mocha : true,
},
extends: [
'eslint:recommended',
],
parser : '@typescript-eslint/parser',
parserOptions : {
ecmaVersion: 'latest',
},
plugins: [
'@typescript-eslint',
'mocha',
],
rules: {
'no-console' : 'off',
'comma-dangle' : 'off',
indent : [ 'warn', 4, {
SwitchCase : 1,
ignoreComments : true,
} ],

'object-curly-spacing' : [ 'warn', 'always' ],
'object-curly-newline' : [ 'warn', { consistent: true, multiline: true } ],
'key-spacing' : [ 'warn', {
align: {
beforeColon : true,
afterColon : true,
on : 'colon',
},
} ],

'space-before-blocks' : [ 'warn' ],
'keyword-spacing' : [ 'warn' ],
'no-multiple-empty-lines' : [ 'warn', { max: 1, maxEOF: 1 } ],
semi : [ 'warn', 'never' ],
'no-unused-vars' : [ 'warn', { ignoreRestSiblings: true } ],
'arrow-spacing' : [ 'warn', { before: true, after: true } ],
'comma-spacing' : [ 'warn', { before: false, after: true } ],
'space-infix-ops' : [ 'warn' ],
'space-in-parens' : [ 'warn', 'always' ],
'array-bracket-spacing' : [ 'warn', 'always' ],
'prefer-const' : 'warn',
'require-await' : 'error'
},
"globals": {
"__dirname": true,
}
}
3 changes: 3 additions & 0 deletions contract/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Makefile
node_modules
build/
60 changes: 60 additions & 0 deletions contract/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@

## Description

[RockPaperScissors](https://github.com/aeternity/state-channel-demo/tree/develop/contract)

## Installation

```bash
$ npm install
```

## Container
```
# to deploy
npx aeproject env init
# to stop container
npx aeproject env --stop
```

## Running the tests

```bash
# tests
$ npm run test

# tets bailing at first fail
$ npm run test:bail

# debug mode
$ npm run test:debug

```

## Deploy

```bash
# deploy on local node
$ npm run deploy

# TODO: work in progress
# deploy on testnet
$ npm run deploy:testnet

# TODO: work in progress
# deploy on testnet
$ npm run deploy:mainnet
```

## Using it in JS projects

```bash
import * as RockPaperScissors from '@aeternity/rock-paper-scissors'

# or

const RockPaperScissors = require('@aeternity/rock-paper-scissors')

```
171 changes: 171 additions & 0 deletions contract/contracts/RockPaperScissors.aes
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
include "String.aes"
include "Option.aes"

payable contract RockPaperScissors =
datatype move = Paper | Rock | Scissors

record state = { player0 : address
, player1 : address
, hash : option(hash)
, last_move_timestamp : int
, player1_move : option(move)
, stake : int
, reaction_time : int
, debug_timestamp : option(int)
}
datatype event
= Init (address /*player0*/ , address /*player1*/ , int /*reaction_time*/ )
| Player0Won (address /*player0*/ , int /*amount won*/ )
| Player1Won (address /*player1*/ , int /*amount won*/ )
| Draw (int /*player0 won*/ , int /*player1 won*/ , string /*player0|player1*/ )
| Player0ProvidedHash (hash /*hash*/ , int /*stake*/ )
| Player0Revealed (string /*move*/ )
| Player1Moved (string /*move*/ , int /*state*/ )
| Player0WonDispute (address /*player0*/ , int /*stake*/ )
| Player1WonDispute (address /*player1*/ , int /*stake*/ )

entrypoint get_state() = state

entrypoint init(player0: address, player1: address, reaction_time: int, debug_timestamp: option(int)) : state =
require(player0 != player1,"use_different_addresses")
require(Call.value == 0, "no_deposit")
Chain.event(Init(player0, player1, reaction_time))
{ player0 = player0
, player1 = player1
, hash = None
, last_move_timestamp = 0
, player1_move = None
, stake = 0
, reaction_time = reaction_time
, debug_timestamp = debug_timestamp
}

payable stateful entrypoint provide_hash(hash: hash): unit =
require_player0()
require(state.hash == None, "already_has_hash")
require(Call.value > 0, "no_stake")
put(state { hash = Some(hash)
, stake = Call.value
, last_move_timestamp = get_timestamp()
})
Chain.event(Player0ProvidedHash(hash,Call.value))

payable stateful entrypoint player1_move(move_str: string): unit =
require_player1()
ensure_player1_turn_to_move()
let move = str_to_move(move_str)
require(Call.value == state.stake,
String.concat("wrong_stake, expected ", Int.to_str(state.stake)))
put(state { player1_move = Some(move)
, last_move_timestamp = get_timestamp()
})
Chain.event(Player1Moved(move_str,Call.value))

stateful entrypoint reveal(key: string, move_str: string): option(address) =
require_player0()
ensure_player0_turn_to_reveal()
let player0_move = str_to_move(move_str)
ensure_if_key_is_valid(key, move_str)
let Some(player1_move) = state.player1_move
let stake = state.stake
Chain.event(Player0Revealed(move_str))
reset_state()
let (player0, player1) = (state.player0, state.player1)
switch(get_winner(player0_move, player1_move))
None =>
let player0_refund = Contract.balance / 2
Chain.spend(player0, player0_refund)
let player1_refund = Contract.balance
Chain.spend(player1, player1_refund) // send the rest of it
Chain.event(Draw(player0_refund, player1_refund,
String.concat(
Address.to_str(player0),
String.concat("|",Address.to_str(player1))
)
))
None
Some(true) =>
Chain.event(Player0Won(player0,Contract.balance))
Chain.spend(player0, Contract.balance)
Some(player0)
Some(false) =>
Chain.event(Player1Won(player1,Contract.balance))
Chain.spend(player1, Contract.balance)
Some(player1)

stateful entrypoint player1_dispute_no_reveal(): unit =
require_player1()
ensure_player0_turn_to_reveal()
ensure_reaction_time()
Chain.event(Player1WonDispute(state.player1,Contract.balance))
Chain.spend(state.player1, Contract.balance)
reset_state()

stateful entrypoint player0_dispute_no_move(): unit =
require_player0()
ensure_player1_turn_to_move()
ensure_reaction_time()
Chain.event(Player0WonDispute(state.player0,Contract.balance))
Chain.spend(state.player0, Contract.balance)
reset_state()

entrypoint compute_hash(key: string, move: string) : hash =
String.sha256(String.concat(key, move))

function ensure_reaction_time() =
require(state.last_move_timestamp + state.reaction_time < get_timestamp(), "not_yet_allowed")

//for debug / test purposes
stateful entrypoint set_timestamp(timestamp: int): unit =
require(state.debug_timestamp != None,"not_debug_mode")
require(timestamp >= 0,"not_positive")
put(state { debug_timestamp = Some(timestamp) })

// internal functions
function get_timestamp() = Option.default(Chain.timestamp, state.debug_timestamp)

function ensure_player1_turn_to_move() =
require(state.hash != None, "no_hash")
require(state.player1_move == None, "there_is_a_move_already")

function ensure_player0_turn_to_reveal() =
require(state.player1_move != None, "there_is_no_move")

function require_player1() =
require(Call.caller == state.player1, "not_player1")

function require_player0() =
require(Call.caller == state.player0, "not_player0")

function ensure_if_key_is_valid(key: string, move: string) =
let computed_hash = compute_hash(key, move)
let Some(stored_hash) = state.hash
require(stored_hash == computed_hash, "invalid_key_and_answer")

function
move_to_str:(move) => string
move_to_str (Rock) = "rock"
move_to_str (Paper) = "paper"
move_to_str (Scissors) = "scissors"

function
str_to_move:(string) => move
str_to_move ("rock") = Rock
str_to_move ("paper") = Paper
str_to_move ("scissors") = Scissors
str_to_move (_) = abort("invalid_move")

function
get_winner:(move , move) => option(bool)
get_winner (a , b) | a == b = None
get_winner (Rock , Scissors) = Some(true)
get_winner (Scissors , Paper) = Some(true)
get_winner (Paper , Rock) = Some(true)
get_winner (_ , _) = Some(false)

stateful function reset_state() =
put(state { hash = None
, last_move_timestamp = 0
, player1_move = None
, stake = 0
})
25 changes: 25 additions & 0 deletions contract/docker-compose.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
version: '3.6'
services:

aeproject_node:
image: aeternity/aeternity:${NODE_TAG}-bundle
hostname: node
environment:
AETERNITY_CONFIG: /home/aeternity/aeternity.yaml
volumes:
- './docker/aeternity.yaml:/home/aeternity/aeternity.yaml'
- './docker/accounts.json:/home/aeternity/node/data/aecore/.genesis/accounts_test.json'

aeproject_compiler:
image: aeternity/aesophia_http:${COMPILER_TAG}
hostname: compiler
ports:
- '3080:3080'

aeproject_proxy:
image: nginx:latest
hostname: proxy
ports:
- '3001:3001'
volumes:
- './docker/nginx.conf:/etc/nginx/conf.d/default.conf'
13 changes: 13 additions & 0 deletions contract/docker/accounts.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
{
"ak_fUq2NesPXcYZ1CcqBcGC3StpdnQw3iVxMA3YSeCNAwfN4myQk": 100000000000000000000000000000000,
"ak_tWZrf8ehmY7CyB1JAoBmWJEeThwWnDpU4NadUdzxVSbzDgKjP": 100000000000000000000000000000000,
"ak_FHZrEbRmanKUe9ECPXVNTLLpRP2SeQCLCT6Vnvs9JuVu78J7V": 100000000000000000000000000000000,
"ak_RYkcTuYcyxQ6fWZsL2G3Kj3K5WCRUEXsi76bPUNkEsoHc52Wp": 100000000000000000000000000000000,
"ak_2VvB4fFu7BQHaSuW5EkQ7GCaM5qiA5BsFUHjJ7dYpAaBoeFCZi": 100000000000000000000000000000000,
"ak_286tvbfP6xe4GY9sEbuN2ftx1LpavQwFVcPor9H4GxBtq5fXws": 100000000000000000000000000000000,
"ak_f9bmi44rdvUGKDsTLp3vMCMLMvvqsMQVWyc3XDAYECmCXEbzy": 100000000000000000000000000000000,
"ak_23p6pT7bajYMJRbnJ5BsbFUuYGX2PBoZAiiYcsrRHZ1BUY2zSF": 100000000000000000000000000000000,
"ak_gLYH5tAexTCvvQA6NpXksrkPJKCkLnB9MTDFTVCBuHNDJ3uZv": 100000000000000000000000000000000,
"ak_zPoY7cSHy2wBKFsdWJGXM7LnSjVt6cn1TWBDdRBUMC7Tur2NQ": 100000000000000000000000000000000,
"ak_RdoCvwe7kxPu2VBv2gQAc1V81sGyTTuxFv36AcvNQYZN7qgut": 0
}
41 changes: 41 additions & 0 deletions contract/docker/aeternity.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
http:
external:
gas_limit: 60000000
internal:
debug_endpoints: true
listen_address: 0.0.0.0
endpoints:
dry-run: true

system:
plugin_path: /home/aeternity/node/plugins
plugins:
- name: aeplugin_dev_mode
config:
keyblock_interval: 0
microblock_interval: 0
auto_emit_microblocks: true

fork_management:
network_id: ae_dev

chain:
persist: true
consensus:
"0":
name: "on_demand"

mining:
beneficiary: "ak_RdoCvwe7kxPu2VBv2gQAc1V81sGyTTuxFv36AcvNQYZN7qgut"
beneficiary_reward_delay: 2
strictly_follow_top: true

websocket:
channel:
port: 3014
listen_address: 0.0.0.0

logging:
# Controls the overload protection in the logs.
hwm: 50
level: debug
Loading

0 comments on commit 168ebfa

Please sign in to comment.