-
Notifications
You must be signed in to change notification settings - Fork 34
EIP-0023 Oracle pool 2.0 #41
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
scalahub
wants to merge
47
commits into
master
Choose a base branch
from
eip23
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from 37 commits
Commits
Show all changes
47 commits
Select commit
Hold shift + click to select a range
3efd0c6
EIP-0022 md file for oracle pool 2.0
scalahub ab5b37a
Rename EIP-22 to EIP-23
scalahub adc4544
Update eip-0023.md
scalahub ddbfbfc
Add EIP-23 to index of EIPs
scalahub 52c467e
Add description of oracle pool 2.0
scalahub ee92d8c
Store participant public keys in R4
scalahub d35140e
Add counter to pool box and fix typos
scalahub 95a2da0
Update the pool box instead of refresh box
scalahub 0ba8625
Fix typos, streamline headings
scalahub 2aecd3c
Add transaction to extract reward tokens
scalahub 731e8ca
Add description of refresh and oracle tx
scalahub 786cc9f
Keep epoch counter in R5 or oracle box
scalahub 0faaa3a
Update eip-0023.md
scalahub 3eb4c2b
Update box creation height in R5 of ballot
scalahub 92e4bf3
Add description of the update mechanism
scalahub 2d21b68
Fix typos, add cross-references to contracts
scalahub 1e4748e
Fix description of update mechanism
scalahub 7c3a2a4
Oracle can change reward tokens in oracle box
scalahub 1a49f9e
Ensure creation height is preserved in update
scalahub d12e62a
Add prerequisites for running own pool
scalahub ac70adb
Fix type in epochLength symbol usage
scalahub bd7a609
Fix typo in eip-0023.md
scalahub 196e89a
Store reward tokens in oracle pool box
scalahub 80de6e9
Add table of contents based on headings
scalahub a9e08d5
Use ballot contract from testing version v2b
scalahub 5d3c178
fix markdown rendering
greenhat 23e2d40
properly fix md rendering;
greenhat abf27ef
Allow preserving pool script during update
scalahub ffb7a76
Update author list for EIP23 (add greenhat)
scalahub 01782c7
Update eip-0023.md
scalahub dabfb0a
Split out EIP-23 contracts into separate files
kettlebell fec79b7
Add encoded contract hashes to EIP-23
kettlebell e485df2
Update eip-0023/eip-0023.md (add link to scastie)
kettlebell f3a32eb
Merge pull request #78 from kettlebell/eip23_separate_contract_files
scalahub c6f5425
Allow changing pool creation height in update
scalahub 1757e00
Merge branch 'master' into eip23
scalahub cae50b7
Update eip-0023/eip-0023.md
scalahub b7138e7
in update contract check reward tokens are either preserved or voted …
greenhat f67124b
update update contract hash according to changes in https://github.co…
greenhat 821fc1f
Fix link to EIP23 readme file
scalahub 21be0b3
Fix link to main readme file
scalahub 2e14357
Remove text on sample token exchange contract
scalahub 2b24dc2
Merge pull request #89 from ergoplatform/eip23-update-contract-reward…
greenhat 3bd226b
let ballot token owner (R4 in ballot box) always spend the ballot box;
greenhat 9cbe17c
update ballot contract hash
greenhat 997b99f
Merge pull request #94 from ergoplatform/eip23-owner-can-spend-ballot
greenhat 2b4a2e4
Update eip-0023/eip-0023.md
kushti File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| { // This box (ballot box): | ||
| // R4 the group element of the owner of the ballot token [GroupElement] | ||
| // R5 the creation height of the update box [Int] | ||
| // R6 the value voted for [Coll[Byte]] | ||
| // R7 the reward token id [Coll[Byte]] | ||
| // R8 the reward token amount [Long] | ||
|
|
||
| val updateNFT = fromBase64("YlFlVGhXbVpxNHQ3dyF6JUMqRi1KQE5jUmZValhuMnI=") // TODO replace with actual | ||
|
|
||
| val minStorageRent = 10000000L // TODO replace with actual | ||
|
|
||
| val selfPubKey = SELF.R4[GroupElement].get | ||
|
|
||
| val outIndex = getVar[Int](0).get | ||
| val output = OUTPUTS(outIndex) | ||
|
|
||
| val isSimpleCopy = output.R4[GroupElement].isDefined && // ballot boxes are transferable by setting different value here | ||
| output.propositionBytes == SELF.propositionBytes && | ||
| output.tokens == SELF.tokens && | ||
| output.value >= minStorageRent | ||
|
|
||
| val update = INPUTS.size > 1 && | ||
| INPUTS(1).tokens.size > 0 && | ||
| INPUTS(1).tokens(0)._1 == updateNFT && // can only update when update box is the 2nd input | ||
| output.R4[GroupElement].get == selfPubKey && // public key is preserved | ||
| output.value >= SELF.value && // value preserved or increased | ||
| ! (output.R5[Any].isDefined) // no more registers; prevents box from being reused as a valid vote | ||
|
|
||
| val owner = proveDlog(selfPubKey) | ||
|
|
||
| // unlike in collection, here we don't require spender to be one of the ballot token holders | ||
| isSimpleCopy && (owner || update) | ||
| } | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,49 @@ | ||
| { // This box (oracle box) | ||
| // R4 public key (GroupElement) | ||
| // R5 epoch counter of current epoch (Int) | ||
| // R6 data point (Long) or empty | ||
|
|
||
| // tokens(0) oracle token (one) | ||
| // tokens(1) reward tokens collected (one or more) | ||
| // | ||
| // When publishing a datapoint, there must be at least one reward token at index 1 | ||
| // | ||
| // We will connect this box to pool NFT in input #0 (and not the refresh NFT in input #1) | ||
| // This way, we can continue to use the same box after updating pool | ||
| // This *could* allow the oracle box to be spent during an update | ||
| // However, this is not an issue because the update contract ensures that tokens and registers (except script) of the pool box are preserved | ||
|
|
||
| // Private key holder can do following things: | ||
| // 1. Change group element (public key) stored in R4 | ||
| // 2. Store any value of type in or delete any value from R4 to R9 | ||
| // 3. Store any token or none at 2nd index | ||
|
|
||
| // In order to connect this oracle box to a different refreshNFT after an update, | ||
| // the oracle should keep at least one new reward token at index 1 when publishing data-point | ||
|
|
||
| val poolNFT = fromBase64("RytLYlBlU2hWbVlxM3Q2dzl6JEMmRilKQE1jUWZUalc=") // TODO replace with actual | ||
|
|
||
| val otherTokenId = INPUTS(0).tokens(0)._1 | ||
|
|
||
| val minStorageRent = 10000000L | ||
| val selfPubKey = SELF.R4[GroupElement].get | ||
| val outIndex = getVar[Int](0).get | ||
| val output = OUTPUTS(outIndex) | ||
|
|
||
| val isSimpleCopy = output.tokens(0) == SELF.tokens(0) && // oracle token is preserved | ||
| output.propositionBytes == SELF.propositionBytes && // script preserved | ||
| output.R4[GroupElement].isDefined && // output must have a public key (not necessarily the same) | ||
| output.value >= minStorageRent // ensure sufficient Ergs to ensure no garbage collection | ||
|
|
||
| val collection = otherTokenId == poolNFT && // first input must be pool box | ||
| output.tokens(1)._1 == SELF.tokens(1)._1 && // reward tokenId is preserved (oracle should ensure this contains a reward token) | ||
| output.tokens(1)._2 > SELF.tokens(1)._2 && // at least one reward token must be added | ||
| output.R4[GroupElement].get == selfPubKey && // for collection preserve public key | ||
| output.value >= SELF.value && // nanoErgs value preserved | ||
| ! (output.R5[Any].isDefined) // no more registers; prevents box from being reused as a valid data-point | ||
|
|
||
| val owner = proveDlog(selfPubKey) | ||
|
|
||
| // owner can choose to transfer to another public key by setting different value in R4 | ||
| isSimpleCopy && (owner || collection) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,16 @@ | ||
| { | ||
| // This box (pool box) | ||
| // epoch start height is stored in creation Height (R3) | ||
| // R4 Current data point (Long) | ||
| // R5 Current epoch counter (Int) | ||
| // | ||
| // tokens(0) pool token (NFT) | ||
| // tokens(1) reward tokens | ||
| // When initializing the box, there must be one reward token. When claiming reward, one token must be left unclaimed | ||
|
|
||
| val otherTokenId = INPUTS(1).tokens(0)._1 | ||
| val refreshNFT = fromBase64("VGpXblpyNHU3eCFBJUQqRy1LYU5kUmdVa1hwMnM1djg=") // TODO replace with actual | ||
| val updateNFT = fromBase64("YlFlVGhXbVpxNHQ3dyF6JUMqRi1KQE5jUmZValhuMnI=") // TODO replace with actual | ||
|
|
||
| sigmaProp(otherTokenId == refreshNFT || otherTokenId == updateNFT) | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,76 @@ | ||
| { // This box (refresh box) | ||
| // | ||
| // tokens(0) refresh token (NFT) | ||
|
|
||
| val oracleTokenId = fromBase64("KkctSmFOZFJnVWtYcDJzNXY4eS9CP0UoSCtNYlBlU2g=") // TODO replace with actual | ||
| val poolNFT = fromBase64("RytLYlBlU2hWbVlxM3Q2dzl6JEMmRilKQE1jUWZUalc=") // TODO replace with actual | ||
| val epochLength = 30 // TODO replace with actual | ||
| val minDataPoints = 4 // TODO replace with actual | ||
| val buffer = 4 // TODO replace with actual | ||
| val maxDeviationPercent = 5 // percent // TODO replace with actual | ||
|
|
||
| val minStartHeight = HEIGHT - epochLength | ||
| val spenderIndex = getVar[Int](0).get // the index of the data-point box (NOT input!) belonging to spender | ||
|
|
||
| val poolIn = INPUTS(0) | ||
| val poolOut = OUTPUTS(0) | ||
| val selfOut = OUTPUTS(1) | ||
|
|
||
| def isValidDataPoint(b: Box) = if (b.R6[Long].isDefined) { | ||
| b.creationInfo._1 >= minStartHeight && // data point must not be too old | ||
| b.tokens(0)._1 == oracleTokenId && // first token id must be of oracle token | ||
| b.R5[Int].get == poolIn.R5[Int].get // it must correspond to this epoch | ||
| } else false | ||
|
|
||
| val dataPoints = INPUTS.filter(isValidDataPoint) | ||
| val pubKey = dataPoints(spenderIndex).R4[GroupElement].get | ||
|
|
||
| val enoughDataPoints = dataPoints.size >= minDataPoints | ||
| val rewardEmitted = dataPoints.size * 2 // one extra token for each collected box as reward to collector | ||
| val epochOver = poolIn.creationInfo._1 < minStartHeight | ||
|
|
||
| val startData = 1L // we don't allow 0 data points | ||
| val startSum = 0L | ||
| // we expect data-points to be sorted in INCREASING order | ||
|
|
||
| val lastSortedSum = dataPoints.fold((startData, (true, startSum)), { | ||
| (t: (Long, (Boolean, Long)), b: Box) => | ||
| val currData = b.R6[Long].get | ||
| val prevData = t._1 | ||
| val wasSorted = t._2._1 | ||
| val oldSum = t._2._2 | ||
| val newSum = oldSum + currData // we don't have to worry about overflow, as it causes script to fail | ||
|
|
||
| val isSorted = wasSorted && prevData <= currData | ||
|
|
||
| (currData, (isSorted, newSum)) | ||
| } | ||
| ) | ||
|
|
||
| val lastData = lastSortedSum._1 | ||
| val isSorted = lastSortedSum._2._1 | ||
| val sum = lastSortedSum._2._2 | ||
| val average = sum / dataPoints.size | ||
|
|
||
| val maxDelta = lastData * maxDeviationPercent / 100 | ||
| val firstData = dataPoints(0).R6[Long].get | ||
|
|
||
| proveDlog(pubKey) && | ||
| epochOver && | ||
| enoughDataPoints && | ||
| isSorted && | ||
| lastData - firstData <= maxDelta && | ||
| poolIn.tokens(0)._1 == poolNFT && | ||
| poolOut.tokens(0) == poolIn.tokens(0) && // preserve pool NFT | ||
| poolOut.tokens(1)._1 == poolIn.tokens(1)._1 && // reward token id preserved | ||
| poolOut.tokens(1)._2 >= poolIn.tokens(1)._2 - rewardEmitted && // reward token amount correctly reduced | ||
| poolOut.tokens.size == poolIn.tokens.size && // cannot inject more tokens to pool box | ||
| poolOut.R4[Long].get == average && // rate | ||
| poolOut.R5[Int].get == poolIn.R5[Int].get + 1 && // counter | ||
| poolOut.propositionBytes == poolIn.propositionBytes && // preserve pool script | ||
| poolOut.value >= poolIn.value && | ||
| poolOut.creationInfo._1 >= HEIGHT - buffer && // ensure that new box has correct start epoch height | ||
| selfOut.tokens == SELF.tokens && // refresh NFT preserved | ||
| selfOut.propositionBytes == SELF.propositionBytes && // script preserved | ||
| selfOut.value >= SELF.value | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,55 @@ | ||
| { // This box (update box): | ||
| // Registers empty | ||
| // | ||
| // ballot boxes (Inputs) | ||
| // R4 the pub key of voter [GroupElement] (not used here) | ||
| // R5 the creation height of this box [Int] | ||
| // R6 the value voted for [Coll[Byte]] (hash of the new pool box script) | ||
| // R7 the reward token id in new box | ||
| // R8 the number of reward tokens in new box | ||
|
|
||
| val poolNFT = fromBase64("RytLYlBlU2hWbVlxM3Q2dzl6JEMmRilKQE1jUWZUalc=") // TODO replace with actual | ||
|
|
||
| val ballotTokenId = fromBase64("P0QoRy1LYVBkU2dWa1lwM3M2djl5JEImRSlIQE1iUWU=") // TODO replace with actual | ||
|
|
||
| val minVotes = 6 // TODO replace with actual | ||
|
|
||
| val poolIn = INPUTS(0) // pool box is 1st input | ||
| val poolOut = OUTPUTS(0) // copy of pool box is the 1st output | ||
|
|
||
| val updateBoxOut = OUTPUTS(1) // copy of this box is the 2nd output | ||
|
|
||
| // compute the hash of the pool output box. This should be the value voted for | ||
| val poolOutHash = blake2b256(poolOut.propositionBytes) | ||
| val rewardTokenId = poolOut.tokens(1)._1 | ||
| val rewardAmt = poolOut.tokens(1)._2 | ||
|
|
||
| val validPoolIn = poolIn.tokens(0)._1 == poolNFT | ||
|
|
||
| val validPoolOut = poolIn.tokens(0) == poolOut.tokens(0) && // NFT preserved | ||
| poolIn.value == poolOut.value && // value preserved | ||
| poolIn.R4[Long] == poolOut.R4[Long] && // rate preserved | ||
| poolIn.R5[Int] == poolOut.R5[Int] && // counter preserved | ||
| ! (poolOut.R6[Any].isDefined) | ||
|
|
||
|
|
||
| val validUpdateOut = updateBoxOut.tokens == SELF.tokens && | ||
| updateBoxOut.propositionBytes == SELF.propositionBytes && | ||
| updateBoxOut.value >= SELF.value && | ||
| updateBoxOut.creationInfo._1 > SELF.creationInfo._1 && | ||
| ! (updateBoxOut.R4[Any].isDefined) | ||
|
|
||
| def isValidBallot(b:Box) = if (b.tokens.size > 0) { | ||
| b.tokens(0)._1 == ballotTokenId && | ||
| b.R5[Int].get == SELF.creationInfo._1 && // ensure vote corresponds to this box by checking creation height | ||
| b.R6[Coll[Byte]].get == poolOutHash && // check proposition voted for | ||
| b.R7[Coll[Byte]].get == rewardTokenId && // check rewardTokenId voted for | ||
| b.R8[Long].get == rewardAmt // check rewardTokenAmt voted for | ||
| } else false | ||
|
|
||
| val ballotBoxes = INPUTS.filter(isValidBallot) | ||
|
|
||
| val votesCount = ballotBoxes.fold(0L, {(accum: Long, b: Box) => accum + b.tokens(0)._2}) | ||
|
|
||
| sigmaProp(validPoolIn && validPoolOut && validUpdateOut && votesCount >= minVotes) | ||
| } |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.