Skip to content

wxdefi-472 #411

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 46 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
af7dd96
draft
ridev6 Sep 25, 2023
6cb5b7e
bot public key on factory
ridev6 Sep 26, 2023
37d6727
fixes
ridev6 Sep 26, 2023
c3b6ea0
init functions
ridev6 Sep 27, 2023
b721ba3
requests draft
ridev6 Sep 27, 2023
0b0701f
fixes
ridev6 Sep 27, 2023
420303a
info update
ridev6 Sep 27, 2023
ede6c39
info fix
ridev6 Sep 27, 2023
2bd0efb
storage info
ridev6 Sep 27, 2023
2ce1294
info fix
ridev6 Sep 27, 2023
0f3951f
info update
ridev6 Sep 27, 2023
4fd2cf8
info update
ridev6 Sep 27, 2023
1c175c3
update info
ridev6 Sep 27, 2023
a6f0e0f
account script instead of hash, request id fix
ridev6 Sep 28, 2023
ca439a2
refactor
ridev6 Sep 29, 2023
9ec595b
refactor
ridev6 Sep 29, 2023
31163b5
allowed pairs
ridev6 Sep 29, 2023
af1c003
move and rename
ridev6 Oct 3, 2023
499dae4
lib
ridev6 Oct 3, 2023
abab8af
fix lib
ridev6 Oct 5, 2023
b650322
chainId to lib
ridev6 Oct 5, 2023
dad8d6d
Merge remote-tracking branch 'origin/main' into wxdefi-472-grid-trading
ridev6 Oct 10, 2023
d3a2896
WIP: grid trading test
ridev6 Oct 10, 2023
011c749
successfull request
ridev6 Oct 10, 2023
4641b10
test: account already exists
ridev6 Oct 10, 2023
460440c
test refactor
ridev6 Oct 10, 2023
06107c5
test account init
ridev6 Oct 11, 2023
28a6c81
account exists test
ridev6 Oct 12, 2023
7dd9b81
account init test fix
ridev6 Oct 12, 2023
b22e8af
account init test refactor
ridev6 Oct 12, 2023
16a8292
common lib fix
ridev6 Oct 12, 2023
cf877af
account fix + permission test
ridev6 Oct 12, 2023
cadfdfb
account set int param
ridev6 Oct 12, 2023
779f40c
change service test
ridev6 Oct 13, 2023
5d04893
service test with 2 accounts
ridev6 Oct 13, 2023
efa34c1
test refactor
ridev6 Oct 13, 2023
246f541
Merge remote-tracking branch 'origin/main' into wxdefi-472-grid-trading
ridev6 Oct 23, 2023
b595986
asset id string + tests
ridev6 Oct 23, 2023
dce52d5
new flow
ridev6 Oct 31, 2023
473e7d9
fix contracts and tests
ridev6 Nov 24, 2023
9102a90
test: add account after request was created
ridev6 Nov 27, 2023
01f2f1b
fix
ridev6 Nov 27, 2023
76df101
Merge remote-tracking branch 'origin/main' into wxdefi-472-grid-trading
ridev6 Dec 7, 2023
866fde0
Merge remote-tracking branch 'origin/main' into wxdefi-472-grid-trading
ridev6 Jan 22, 2024
c7b6ffa
fix removed common.lib.ride
ridev6 Jan 22, 2024
04ace31
Merge remote-tracking branch 'origin/main' into wxdefi-472-grid-trading
ridev6 Feb 28, 2024
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
99 changes: 99 additions & 0 deletions ride/grid_trading_account.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
{-# STDLIB_VERSION 7 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

let separator = "__"
let chainId = this.bytes.drop(1).take(1)
let chainIdT = base16'54'
let chainIdW = base16'57'

func mustAddress(i: Invocation, address: Address) = {
i.caller == address || throw("permission denied")
}

func mustThis(i: Invocation) = {
mustAddress(i, this)
}


let kFactoryPublicKey = "%s__factoryPublicKey"
let kServicePublicKey = "%s__servicePublicKey"
let kOwnerPublicKey = "%s__ownerPublicKey"
let kBotPublicKey = "%s__botPublicKey"

func kAccountAddressToRequestId(accountAddress: Address) = {
["%s%s", accountAddress.toString(), "accountAddressToRequestId"].makeString(separator)
}
func kAccountOwner(requestId: String) = {
["%s%s", requestId, "ownerPublicKey"].makeString(separator)
}

let factoryAddress = addressFromPublicKey(this.getBinaryValue(kFactoryPublicKey))
let serviceAddress = addressFromPublicKey(factoryAddress.getBinaryValue(kServicePublicKey))

let requestId = factoryAddress.getStringValue(kAccountAddressToRequestId(this))
let ownerPublicKey = factoryAddress.getBinaryValue(kAccountOwner(requestId))
let ownerAddress = addressFromPublicKey(ownerPublicKey)

func mustService(i: Invocation) = {
mustAddress(i, serviceAddress)
}

func mustOwner(i: Invocation) = {
mustAddress(i, ownerAddress)
}

@Callable(i)
func stringEntry(key: String, val: String) =
if (i.mustService()) then ([StringEntry(key, val)], key) else ([], unit)

@Callable(i)
func integerEntry(key: String, val: Int) =
if (i.mustService()) then ([IntegerEntry(key, val)], key) else ([], unit)

@Callable(i)
func transferAsset(recipientBytes: ByteVector, amount: Int, assetId: ByteVector) =
if (i.mustService()) then ([ScriptTransfer(Address(recipientBytes), amount, assetId)], amount) else ([], unit)

@Callable(i)
func transferWaves(recipientBytes: ByteVector, amount: Int) =
if (i.mustService()) then ([ScriptTransfer(Address(recipientBytes), amount, unit)], amount) else ([], unit)

@Callable(i)
func init(factoryPublicKey: ByteVector, creatorPublicKey: ByteVector) = {
strict checkCaller = i.mustThis()

# throws if accounts is not ok
strict completeRequest = addressFromPublicKey(
factoryPublicKey
).invoke("addAccount", [creatorPublicKey], [])

([
BinaryEntry(kFactoryPublicKey, factoryPublicKey)
], unit)
}

# owner can call only this function
# service address should be replaced to change logic
@Callable(i)
func call(function: String, args: List[String]) = {
strict checkCaller = i.mustOwner()
let result = serviceAddress.reentrantInvoke(function, [args], i.payments)

(nil, result)
}

# bot can trade
@Verifier(tx)
func verify() = {
let botPublicKey = factoryAddress.getBinaryValue(kBotPublicKey)
match tx {
case _: Order => sigVerify(tx.bodyBytes, tx.proofs[0], botPublicKey)
case _: InvokeScriptTransaction => if (this.getBinary(kFactoryPublicKey).isDefined()) then false else {
sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}
case _ => if (chainId == chainIdW) then false else {
sigVerify(tx.bodyBytes, tx.proofs[0], tx.senderPublicKey)
}
}
}
193 changes: 193 additions & 0 deletions ride/grid_trading_factory.ride
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
{-# STDLIB_VERSION 7 #-}
{-# CONTENT_TYPE DAPP #-}
{-# SCRIPT_TYPE ACCOUNT #-}

let separator = "__"
let chainId = this.bytes.drop(1).take(1)
let chainIdT = base16'54'
let chainIdW = base16'57'

func mustAddress(i: Invocation, address: Address) = {
i.caller == address || throw("permission denied")
}

func mustThis(i: Invocation) = {
mustAddress(i, this)
}


let wavesString = "WAVES"
let queueItemSize = 32

func parseAssetId(input: String) = {
if (input == wavesString) then unit else input.fromBase58String()
}

func assetIdToString(input: ByteVector|Unit) = {
if (input == unit) then wavesString else input.value().toBase58String()
}

let kServicePublicKey = "%s__servicePublicKey"
let kBotPublicKey = "%s__botPublicKey"

let kAccountScript = "%s__accountScript"
func accountScript() = this.getBinary(
kAccountScript
).valueOrErrorMessage("account script is not set")

let kRewardAmount = "%s__rewardAmount"
func rewardAmount() = this.getInteger(
kRewardAmount
).valueOrErrorMessage("reward amount is not set")

let REQUEST_STATUS_EMPTY = 0
let REQUEST_STATUS_READY = 1
func kRequestStatus(requestId: ByteVector) = {
["%s%s", requestId.toBase58String(), "status"].makeString(separator)
}

func kAccountCreatorPublicKey(accountAddress: Address) = {
["%s%s", accountAddress.toString(), "creatorPublicKey"].makeString(separator)
}
func kRequestOwnerPublicKey(requestId: ByteVector) = {
["%s%s", requestId.toBase58String(), "ownerPublicKey"].makeString(separator)
}
func kRequestAmountAssetId(requestId: ByteVector) = {
["%s%s", requestId.toBase58String(), "amountAssetId"].makeString(separator)
}
func kRequestPriceAssetId(requestId: ByteVector) = {
["%s%s", requestId.toBase58String(), "priceAssetId"].makeString(separator)
}
func kRequestIdToAccountPublicKey(requestId: ByteVector) = {
["%s%s", requestId.toBase58String(), "requestIdToAccountPublicKey"].makeString(separator)
}
func kAccountAddressToRequestId(accountAddress: Address) = {
["%s%s", accountAddress.toString(), "accountAddressToRequestId"].makeString(separator)
}

func kRequestsQueue() = {
["%s", "requestsQueue"].makeString(separator)
}
func requestsQueue() = this.getBinary(kRequestsQueue()).valueOrElse(base58'')

func kAccountsQueue() = {
["%s", "accountsQueue"].makeString(separator)
}
func accountsQueue() = this.getBinary(kAccountsQueue()).valueOrElse(base58'')

func kPairAllowed(amountAssetId: ByteVector|Unit, priceAssetId: ByteVector|Unit) = {
["%s%s%s", assetIdToString(amountAssetId), assetIdToString(priceAssetId), "pairAllowed"].makeString(separator)
}
func pairAllowed(amountAssetId: ByteVector|Unit, priceAssetId: ByteVector|Unit) = {
this.getBoolean(
kPairAllowed(amountAssetId, priceAssetId)
).valueOrElse(false)
}

# can be updated by voting
let serviceAddress = addressFromPublicKey(this.getBinaryValue(kServicePublicKey))
let botPublicKey = this.getBinaryValue(kBotPublicKey)

@Callable(i)
func init(
servicePublicKey: ByteVector,
botPublicKey: ByteVector,
accountScript: ByteVector,
rewardAmount: Int
) = {
strict checkCaller = i.mustThis()

([
BinaryEntry(kServicePublicKey, servicePublicKey),
BinaryEntry(kBotPublicKey, botPublicKey),
BinaryEntry(kAccountScript, accountScript),
IntegerEntry(kRewardAmount, rewardAmount)
], unit)
}

# called by user
# additional fee in payment
@Callable(i)
func requestAccount(amountAssetIdStr: String, priceAssetIdStr: String) = {
let requestId = sha256(i.caller.bytes + amountAssetIdStr.fromBase58String() + priceAssetIdStr.fromBase58String())
let amountAssetId = parseAssetId(amountAssetIdStr)
let priceAssetId = parseAssetId(priceAssetIdStr)

strict checks = [
i.payments.size() == 1 || throw("1 payment is required"),
i.payments[0].assetId == unit || throw("invalid asset"),
i.payments[0].amount == rewardAmount() || throw("invalid amount"),
pairAllowed(amountAssetId, priceAssetId) || throw("pair is not allowed"),
this.getInteger(kRequestStatus(requestId)) == unit || throw("account is already exists")
]

# add request to queue or match with account immediately

let actions = if (accountsQueue().size() == 0) then { # if the accounts queue is empty
# then add request to requests queue
[
IntegerEntry(kRequestStatus(requestId), REQUEST_STATUS_EMPTY),
BinaryEntry(kRequestsQueue(), requestsQueue() + requestId)
]
} else { # if the accounts queue is not empty
# then fullfill request
let accountPublicKey = accountsQueue().take(queueItemSize)
let accountAddress = addressFromPublicKey(accountPublicKey)
let creatorAddress = addressFromPublicKey(
this.getBinary(
kAccountCreatorPublicKey(accountAddress)
).valueOrErrorMessage("invalid creator public key")
)
[
BinaryEntry(kAccountsQueue(), accountsQueue().drop(queueItemSize)),
IntegerEntry(kRequestStatus(requestId), REQUEST_STATUS_READY),
BinaryEntry(kRequestIdToAccountPublicKey(requestId), accountPublicKey),
StringEntry(kAccountAddressToRequestId(accountAddress), requestId.toBase58String()),
ScriptTransfer(creatorAddress, rewardAmount(), unit)
]
}

(actions ++ [
BinaryEntry(kRequestOwnerPublicKey(requestId), i.callerPublicKey),
StringEntry(kRequestAmountAssetId(requestId), amountAssetIdStr),
StringEntry(kRequestPriceAssetId(requestId), priceAssetIdStr)
], unit)
}

# called by account script
# additional fee is sent to recipient
@Callable(i)
func addAccount(creatorPublicKey: ByteVector) = {
let accountPublicKey = i.callerPublicKey
let accountAddress = i.caller
let creatorAddress = addressFromPublicKey(creatorPublicKey)

strict checks = [
this.getBinary(kAccountCreatorPublicKey(accountAddress)) == unit || throw("account is already exists"),
match scriptHash(accountAddress) {
case b: ByteVector => b == blake2b256_32Kb(accountScript())
case _ => false
} || throw("invalid script")
]

let actions = if (requestsQueue().size() == 0) then { # if the requests queue is empty
# then add account to accounts queue
[
BinaryEntry(kAccountsQueue(), accountsQueue() + accountPublicKey)
]
} else { # if the requests queue is not empty
# then fullfill next request
let requestId = requestsQueue().take(queueItemSize)
[
BinaryEntry(kRequestsQueue(), requestsQueue().drop(queueItemSize)),
IntegerEntry(kRequestStatus(requestId), REQUEST_STATUS_READY),
BinaryEntry(kRequestIdToAccountPublicKey(requestId), accountPublicKey),
StringEntry(kAccountAddressToRequestId(accountAddress), requestId.toBase58String()),
ScriptTransfer(creatorAddress, rewardAmount(), unit)
]
}

(actions ++ [
BinaryEntry(kAccountCreatorPublicKey(accountAddress), creatorPublicKey)
], unit)
}
77 changes: 77 additions & 0 deletions ride/grid_trading_info.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
### Participants

- factory
- service address in storage can be changed by the voting
- owner
- can call the account callable functions
- account
- allow orders signed by bot
- pass functions to service
- script can't be updated
- bot
- can use account for trading
- service
- script can't be updated
- functions for account

### Storage

#### Factory

| key | type | description |
| ------------------------------------------------------ | ------------ | ------------------------------------- |
| `%s__servicePublicKey` | `ByteVector` | Service public key |
| `%s__botPublicKey` | `ByteVector` | Bot public key |
| `%s__accountScript` | `ByteVector` | Allowed account script |
| `%s__rewardAmount` | `Int` | Reward amount |
| `%s%s__<accountId>__status` | `Integer` | Account status (0 - empty, 1 - ready) |
| `%s%s__<accountId>__ownerPublicKey` | `ByteVector` | Account owner |
| `%s%s__<accountId>__creatorPublicKey` | `ByteVector` | Account creator |
| `%s%s__<accountId>__amountAssetId` | `ByteVector` | Account amount asset id |
| `%s%s__<accountId>__priceAssetId` | `ByteVector` | Account price asset id |
| `%s%s__<accountId>__accountIdToAccountPublicKey` | `ByteVector` | Account id → account public key |
| `%s%s__<accountAddress>__accountAddressToAccountId` | `String` | Account address → account id |
| `%s%s%s__<amountAssetId>__<priceAssetId>__pairAllowed` | `Boolean` | Pair allowed |

#### Account

| key | type | description |
| ---------------------- | ------------ | ------------------ |
| `%s__factoryPublicKey` | `ByteVector` | Factory public key |

### Account creation

```mermaid
sequenceDiagram
User ->> Factory: request(amount asset id, price asset id)
activate Factory
Note over User, Factory: payment = reward amount
Note over Factory: account id = sha256(owner + amount asset id + price asset id)
Note over Factory: status = 0, save owner, save assets ids
Factory -->> Creator: new request
deactivate Factory
activate Creator
Creator -->> Account: set script
deactivate Creator
Account ->> Account: init(requestId, factoryPublicKey, creatorPublicKey)
activate Account
Account ->> Factory: complete request with account
activate Factory
Note over Factory: check account script, status
Note over Factory: save creator, status = 1
Factory -->> Account: ok
Note over Account: save factory in state thus lock script
deactivate Account
Factory ->> Creator: transfer reward to creator
deactivate Factory
```

### Withdraw

```mermaid
sequenceDiagram
User ->> Account: call(withdraw, recipient, amount)
Account ->> Service: withdraw(recipient, amount)
Service ->> Account: transfer(recipient, amount)
Account ->> User: transfer specified amount
```
Loading