rchain-token
Version:
Fungibles and non-fungibles tokens on the RChain blockchain
1,251 lines (1,175 loc) • 103 kB
JavaScript
/* GENERATED CODE, only edit rholang/*.rho files*/
module.exports.masterTerm = (payload) => {
return `new
basket,
prefixCh,
entryCh,
entryUriCh,
revVaultCh,
makePurseCh,
creditBackCh,
calculateFeeCh,
validateStringCh,
initializeOCAPOnBoxCh,
initLocksForContractCh,
initLocksForBoxCh,
appendLogsForContract,
/*
self is the ultimate local unforgeable in
master contract, every data is stored in channels that
derives from *self unforgeable name
// tree hash map of purses :
thm <- @(*self, "purses", "contract03")
// tree hash map of purses data :
thm <- @(*self, "pursesData", "contract03")
// contract's configs
config <- @(*self, "contractConfig", "contract03")
// box's configs
config <- @(*self, "boxConfig", "box01")
// boxes (rholang Map)
box <- @(*self, "boxes", "box01")
// super keys of a given box
superKeys <- @(*self, "boxesSuperKeys", "box01")
*/
self,
/*
boxesThm and contractsThm only store the list
of existing contracts / boxes, ex:
boxesThm:
{ "box1": "exists", "mycoolbox": "exists" }
Then each box is a Map stored at a unique channel
(see above) and has the following structure:
{
[contractId: string]: Set(purseId: string)
}
Each contract has its own tree hash map, and
have the following structure:
pursesThm, example of FT purses:
{
"1": { quantity: 2, timestamp: 12562173658, boxId: "box1", price: Nil},
"2": { quantity: 12, timestamp: 12562173658, boxId: "box1", price: 2},
}
*/
boxesReadyCh,
contractsReadyCh,
TreeHashMap,
savePurseInBoxCh,
removePurseInBoxCh,
getBoxCh,
getPurseWithAtLeastQuantityCh,
getPurseCh,
getContractPursesThmCh,
getContractPursesDataThmCh,
insertArbitrary(\`rho:registry:insertArbitrary\`),
stdout(\`rho:io:stdout\`),
revAddress(\`rho:rev:address\`),
registryLookup(\`rho:registry:lookup\`),
blockData(\`rho:block:data\`)
in {
// This line should be replaced by tree hash map implementation
// reimplementation of TreeHashMap
/*
Communications between channels have generally been reduced to reduce amount of
serialization / deserialization
when you "init" you can choose that the processes are also stored as bytes, instead of storing a map for each node, it stores a map at channel @map, and bytes at channel @(map, "bytes), this will make the "getAllValues" 10x, 20x, 30x faster depending on the process you are storing
!!! make sure your processes do not contain the string "£$£$", or the bytes c2a324c2a324, those are used as delimiters
*/
new MakeNode, ByteArrayToNybbleList, TreeHashMapSetter, TreeHashMapGetter, TreeHashMapUpdater, HowManyPrefixes, NybbleListForI, RemoveBytesSectionIfExistsCh, keccak256Hash(\`rho:crypto:keccak256Hash\`), powersCh, storeToken, nodeGet in {
match ([1,2,4,8,16,32,64,128,256,512,1024,2048,4096,8192,16384,32768,655256], ["00","01","02","03","04","05","06","07","08","09","0a","0b","0c","0d","0e","0f","10","11","12","13","14","15","16","17","18","19","1a","1b","1c","1d","1e","1f","20","21","22","23","24","25","26","27","28","29","2a","2b","2c","2d","2e","2f","30","31","32","33","34","35","36","37","38","39","3a","3b","3c","3d","3e","3f","40","41","42","43","44","45","46","47","48","49","4a","4b","4c","4d","4e","4f","50","51","52","53","54","55","56","57","58","59","5a","5b","5c","5d","5e","5f","60","61","62","63","64","65","66","67","68","69","6a","6b","6c","6d","6e","6f","70","71","72","73","74","75","76","77","78","79","7a","7b","7c","7d","7e","7f","80","81","82","83","84","85","86","87","88","89","8a","8b","8c","8d","8e","8f","90","91","92","93","94","95","96","97","98","99","9a","9b","9c","9d","9e","9f","a0","a1","a2","a3","a4","a5","a6","a7","a8","a9","aa","ab","ac","ad","ae","af","b0","b1","b2","b3","b4","b5","b6","b7","b8","b9","ba","bb","bc","bd","be","bf","c0","c1","c2","c3","c4","c5","c6","c7","c8","c9","ca","cb","cc","cd","ce","cf","d0","d1","d2","d3","d4","d5","d6","d7","d8","d9","da","db","dc","dd","de","df","e0","e1","e2","e3","e4","e5","e6","e7","e8","e9","ea","eb","ec","ed","ee","ef","f0","f1","f2","f3","f4","f5","f6","f7","f8","f9","fa","fb","fc","fd","fe","ff"], 12) {
(powers, hexas, base) => {
contract MakeNode(@initVal, @node) = {
@[node, *storeToken]!(initVal)
} |
contract nodeGet(@node, ret) = {
for (@val <<- @[node, *storeToken]) {
ret!(val)
}
} |
contract HowManyPrefixes(@map, ret) = {
for (@depth <<- @(map, "depth")) {
match depth {
1 => ret!(base)
2 => ret!(base * base)
3 => ret!(base * base * base)
4 => ret!(base * base * base * base)
}
}
} |
contract NybbleListForI(@map, @i, @depth, ret) = {
match depth {
1 => {
match hexas.nth(i % base) {
str => {
ByteArrayToNybbleList!(str.hexToBytes(), 0, depth, [], *ret)
}
}
}
2 => {
match hexas.nth(i / base) ++ hexas.nth(i % base) {
str => {
ByteArrayToNybbleList!(str.hexToBytes(), 0, depth, [], *ret)
}
}
}
3 => {
match hexas.nth(i / base / base) ++ hexas.nth(i / base) ++ hexas.nth(i % base) {
str => {
ByteArrayToNybbleList!(str.hexToBytes(), 0, depth, [], *ret)
}
}
}
4 => {
match hexas.nth(i / base / base / base) ++ hexas.nth(i / base / base) ++ hexas.nth(i / base) ++ hexas.nth(i % base) {
str => {
ByteArrayToNybbleList!(str.hexToBytes(), 0, depth, [], *ret)
}
}
}
}
} |
contract ByteArrayToNybbleList(@ba, @n, @len, @acc, ret) = {
if (n == len) {
ret!(acc)
} else {
ByteArrayToNybbleList!(ba, n+1, len, acc ++ [ ba.nth(n) % base ], *ret)
}
} |
contract TreeHashMap(@"init", @depth, @alsoStoreAsBytes, ret) = {
new map in {
MakeNode!(0, (*map, [])) |
if (alsoStoreAsBytes == true) {
MakeNode!(0, ((*map, "bytes"), []))
} |
@(*map, "depth")!!(depth) |
@(*map, "alsoStoreAsBytes")!!(alsoStoreAsBytes) |
ret!(*map)
}
} |
contract TreeHashMapGetter(@map, @nybList, @n, @len, @suffix, ret) = {
// Look up the value of the node at (map, nybList.slice(0, n + 1))
for (@val <<- @[(map, nybList.slice(0, n)), *storeToken]) {
if (n == len) {
ret!(val.get(suffix))
} else {
// Otherwise check if the rest of the path exists.
// Bit k set means node k exists.
// nybList.nth(n) is the node number
// val & powers.nth(nybList.nth(n)) is nonzero if the node exists
// (val / powers.nth(nybList.nth(n))) % 2 is 1 if the node exists
if ((val / powers.nth(nybList.nth(n))) % 2 == 0) {
ret!(Nil)
} else {
TreeHashMapGetter!(map, nybList, n + 1, len, suffix, *ret)
}
}
}
} |
contract TreeHashMap(@"get", @map, @key, ret) = {
new hashCh, nybListCh in {
// Hash the key to get a 256-bit array
keccak256Hash!(key.toByteArray(), *hashCh) |
for (@hash <- hashCh) {
for (@depth <<- @(map, "depth")) {
// Get the bit list
ByteArrayToNybbleList!(hash, 0, depth, [], *nybListCh) |
for (@nybList <- nybListCh) {
TreeHashMapGetter!(map, nybList, 0, depth, hash.slice(depth, 32), *ret)
}
}
}
}
} |
// not used anymore, now getValuesAtIndex is used to get all values
// this way we avoid iteration
contract TreeHashMap(@"getAllValues", @map, ret) = {
new howManyPrefixesCh, iterateOnPrefixesCh, nybListCh in {
HowManyPrefixes!(map, *howManyPrefixesCh) |
for (@depth <<- @(map, "depth")) {
for (@alsoStoreAsBytes <<- @(map, "alsoStoreAsBytes")) {
for (@howManyPrefixes <- howManyPrefixesCh ) {
contract iterateOnPrefixesCh() = {
new itCh, bytesOrMapCh, TreeHashMapGetterValues in {
// do not move it up, the goal is reduce the number of serializatin / dezerialization
contract TreeHashMapGetterValues(@channel, @nybList, @n, @len, @i) = {
// channel is either map or (map, "bytes")
// Look up the value of the node at (channel, nybList.slice(0, n + 1))
for (@val <<- @[(channel, nybList.slice(0, n)), *storeToken]) {
if (n == len) {
if (val == Nil) {
itCh!(i + 1)
} else {
if (alsoStoreAsBytes == true) {
for (@bytes <- bytesOrMapCh) {
itCh!(i + 1) |
// store-as-bytes-map
bytesOrMapCh!(bytes.union(val))
// store-as-bytes-array
/* if (bytes == Nil) {
bytesOrMapCh!(bytes)
} else {
bytesOrMapCh!(bytes ++ val)
} */
}
} else {
for (@map <- bytesOrMapCh) {
bytesOrMapCh!(map.union(val)) |
itCh!(i + 1)
}
}
}
} else {
// Otherwise check if the rest of the path exists.
// Bit k set means node k exists.
// nybList.nth(n) is the node number
// val & powers.nth(nybList.nth(n)) is nonzero if the node exists
// (val / powers.nth(nybList.nth(n))) % 2 is 1 if the node exists
if ((val / powers.nth(nybList.nth(n))) % 2 == 0) {
itCh!(i + 1)
} else {
TreeHashMapGetterValues!(channel, nybList, n + 1, len, i)
}
}
}
} |
for (@i <= itCh) {
match i <= howManyPrefixes - 1 {
false => {
for (@a <- bytesOrMapCh) {
ret!(a)
}
}
true => {
NybbleListForI!(map, i, depth, *nybListCh) |
for (@nybList <- nybListCh) {
if (alsoStoreAsBytes == true) {
TreeHashMapGetterValues!((map, "bytes"), nybList, 0, depth, i)
} else {
TreeHashMapGetterValues!(map, nybList, 0, depth, i)
}
}
}
}
} |
if (alsoStoreAsBytes == true) {
// store-as-bytes-map
bytesOrMapCh!({})
// store-as-bytes-array
/* bytesOrMapCh!(Nil) */
} else {
bytesOrMapCh!({})
} |
itCh!(0)
}
} |
iterateOnPrefixesCh!()
}
}
}
}
} |
contract TreeHashMap(@"getValuesAtIndex", @map, @i, ret) = {
new howManyPrefixesCh, nybListCh in {
HowManyPrefixes!(map, *howManyPrefixesCh) |
for (@depth <<- @(map, "depth")) {
for (@alsoStoreAsBytes <<- @(map, "alsoStoreAsBytes")) {
for (@howManyPrefixes <- howManyPrefixesCh ) {
new TreeHashMapGetterValues in {
// do not move it up, the goal is reduce the number of serializatin / dezerialization
contract TreeHashMapGetterValues(@channel, @nybList, @n, @len, @i) = {
// channel is either map or (map, "bytes")
// Look up the value of the node at (channel, nybList.slice(0, n + 1))
for (@val <<- @[(channel, nybList.slice(0, n)), *storeToken]) {
if (n == len) {
ret!(val)
} else {
// Otherwise check if the rest of the path exists.
// Bit k set means node k exists.
// nybList.nth(n) is the node number
// val & powers.nth(nybList.nth(n)) is nonzero if the node exists
// (val / powers.nth(nybList.nth(n))) % 2 is 1 if the node exists
if ((val / powers.nth(nybList.nth(n))) % 2 == 0) {
ret!({})
} else {
TreeHashMapGetterValues!(channel, nybList, n + 1, len, i)
}
}
}
} |
NybbleListForI!(map, i, depth, *nybListCh) |
for (@nybList <- nybListCh) {
if (alsoStoreAsBytes == true) {
TreeHashMapGetterValues!((map, "bytes"), nybList, 0, depth, i)
} else {
TreeHashMapGetterValues!(map, nybList, 0, depth, i)
}
}
}
}
}
}
}
} |
contract TreeHashMapSetter(@channel, @nybList, @n, @len, @newVal, @suffix, ret) = {
// channel is either map or (map, "bytes")
// Look up the value of the node at (channel, nybList.slice(0, n + 1))
new valCh, restCh in {
match (channel, nybList.slice(0, n)) {
node => {
for (@val <<- @[node, *storeToken]) {
if (n == len) {
// Acquire the lock on this node
for (@val <- @[node, *storeToken]) {
// If we're at the end of the path, set the node to newVal.
if (val == 0) {
// Release the lock
@[node, *storeToken]!({suffix: newVal}) |
// Return
ret!(Nil)
}
else {
// Release the lock
if (newVal == Nil) {
@[node, *storeToken]!(val.delete(suffix)) |
// Return
ret!(Nil)
} else {
@[node, *storeToken]!(val.set(suffix, newVal)) |
// Return
ret!(Nil)
}
}
}
} else {
// Otherwise make the rest of the path exist.
// Bit k set means child node k exists.
if ((val/powers.nth(nybList.nth(n))) % 2 == 0) {
// Child node missing
// Acquire the lock
for (@val <- @[node, *storeToken]) {
// Re-test value
if ((val/powers.nth(nybList.nth(n))) % 2 == 0) {
// Child node still missing
// Create node, set node to 0
MakeNode!(0, (channel, nybList.slice(0, n + 1))) |
// Update current node to val | (1 << nybList.nth(n))
match nybList.nth(n) {
bit => {
// val | (1 << bit)
// Bitwise operators would be really nice to have!
// Release the lock
@[node, *storeToken]!((val % powers.nth(bit)) +
(val / powers.nth(bit + 1)) * powers.nth(bit + 1) +
powers.nth(bit))
}
} |
// Child node now exists, loop
TreeHashMapSetter!(channel, nybList, n + 1, len, newVal, suffix, *ret)
} else {
// Child node created between reads
// Release lock
@[node, *storeToken]!(val) |
// Loop
TreeHashMapSetter!(channel, nybList, n + 1, len, newVal, suffix, *ret)
}
}
} else {
// Child node exists, loop
TreeHashMapSetter!(channel, nybList, n + 1, len, newVal, suffix, *ret)
}
}
}
}
}
}
} |
contract TreeHashMap(@"set", @map, @key, @newVal, ret) = {
new hashCh, nybListCh in {
// Hash the key to get a 256-bit array
keccak256Hash!(key.toByteArray(), *hashCh) |
for (@hash <- hashCh) {
for (@depth <<- @(map, "depth")) {
for (@alsoStoreAsBytes <<- @(map, "alsoStoreAsBytes")) {
ByteArrayToNybbleList!(hash, 0, depth, [], *nybListCh) |
// Get the bit list
for (@nybList <- nybListCh) {
if (alsoStoreAsBytes == true) {
new ret1, ret2 in {
if (newVal == Nil) {
TreeHashMapSetter!((map, "bytes"), nybList, 0, depth, Nil, hash.slice(depth, 32), *ret2)
} else {
TreeHashMapSetter!((map, "bytes"), nybList, 0, depth, newVal.toByteArray(), hash.slice(depth, 32), *ret2)
} |
TreeHashMapSetter!(map, nybList, 0, depth, newVal, hash.slice(depth, 32), *ret1) |
for (_ <- ret1; _ <- ret2) {
ret!(Nil)
}
}
} else {
TreeHashMapSetter!(map, nybList, 0, depth, newVal, hash.slice(depth, 32), *ret)
}
}
}
}
}
}
} |
contract TreeHashMapUpdater(@map, @nybList, @n, @len, update, @suffix, ret) = {
// Look up the value of the node at [map, nybList.slice(0, n + 1)
new valCh in {
match (map, nybList.slice(0, n)) {
node => {
for (@val <<- @[node, *storeToken]) {
if (n == len) {
// We're at the end of the path.
if (val == 0) {
// There's nothing here.
// Return
ret!(Nil)
} else {
new resultCh in {
// Acquire the lock on this node
for (@val <- @[node, *storeToken]) {
// Update the current value
update!(val.get(suffix), *resultCh) |
for (@newVal <- resultCh) {
// Release the lock
if (newVal == Nil) {
@[node, *storeToken]!(val.delete(suffix)) |
// Return
ret!(Nil)
} else {
@[node, *storeToken]!(val.set(suffix, newVal)) |
// Return
ret!(Nil)
}
}
}
}
}
} else {
// Otherwise try to reach the end of the path.
// Bit k set means child node k exists.
if ((val/powers.nth(nybList.nth(n))) % 2 == 0) {
// If the path doesn't exist, there's no value to update.
// Return
ret!(Nil)
} else {
// Child node exists, loop
TreeHashMapUpdater!(map, nybList, n + 1, len, *update, suffix, *ret)
}
}
}
}
}
}
} |
contract TreeHashMap(@"update", @map, @key, update, ret) = {
new hashCh, nybListCh, keccak256Hash(\`rho:crypto:keccak256Hash\`) in {
// Hash the key to get a 256-bit array
keccak256Hash!(key.toByteArray(), *hashCh) |
for (@hash <- hashCh) {
for (@depth <<- @(map, "depth")) {
for (@alsoStoreAsBytes <<- @(map, "alsoStoreAsBytes")) {
// Get the bit list
ByteArrayToNybbleList!(hash, 0, depth, [], *nybListCh) |
for (@nybList <- nybListCh) {
if (alsoStoreAsBytes == true) {
new ret1, ret2, updateWrapper, retWrapper, updateWrapperBytes in {
for (@val, ret <- updateWrapper; _, retBytes <- updateWrapperBytes) {
update!(val, *retWrapper) |
for (@newVal <- retWrapper) {
ret!(newVal) |
retBytes!(newVal.toByteArray())
}
} |
TreeHashMapUpdater!((map, "bytes"), nybList, 0, depth, *updateWrapperBytes, hash.slice(depth, 32), *ret1) |
TreeHashMapUpdater!(map, nybList, 0, depth, *updateWrapper, hash.slice(depth, 32), *ret2) |
for (_ <- ret1; _ <- ret2) {
ret!(Nil)
}
}
} else {
TreeHashMapUpdater!(map, nybList, 0, depth, *update, hash.slice(depth, 32), *ret)
}
}
}
}
}
}
}
}
}
} |
// depth 1 = 12 maps in tree hash map
// depth 2 = 12 * 12 = 144 maps in tree hash map
// etc...
// Global lock for REGISTER_CONTRACT and REGISTER_BOX
// do not allow concurrency for those operations
@(*self, "REGISTER_CONTRACT_LOCK")!(Nil) |
@(*self, "REGISTER_BOX_LOCK")!(Nil) |
// Scoped locks for WITHDRAW, SWAP and CREATE_PURSE, they can touch
// the same boxes or purses, we cannot allow concurrency because we
// want to avoid race conditions
for (@boxId <= initLocksForBoxCh) {
@(*self, "BOX_LOCK", boxId)!(Nil)
} |
for (@contractId <= initLocksForContractCh) {
@(*self, "CONTRACT_LOCK", contractId)!(Nil)
} |
for (@(contractId, type, str) <= appendLogsForContract) {
new blockDataCh in {
blockData!(*blockDataCh) |
for (_, @timestamp, _ <- blockDataCh) {
for (@current <- @(*self, "LOGS", contractId)) {
match current.length() > 2600 {
true => {
@(*self, "LOGS", contractId)!("\${type},\${ts}," %% { "type": type, "ts": timestamp } ++ str ++ current.slice(0,2200))
}
false => {
@(*self, "LOGS", contractId)!("\${type},\${ts}," %% { "type": type, "ts": timestamp } ++ str ++ current)
}
}
}
}
}
} |
// those two tree hash maps store the existence of
// boxes or contracts
// { "box1": "exists", "box2": "exists" }
TreeHashMap!("init", ${payload.depth || 3}, true, *boxesReadyCh) |
// { "contract1": "exists", "contract2": "exists" }
TreeHashMap!("init", ${payload.depth || 3}, false, *contractsReadyCh) |
registryLookup!(\`rho:rchain:revVault\`, *revVaultCh) |
for (@boxesThm <- boxesReadyCh; @contractsThm <- contractsReadyCh; @(_, RevVault) <- revVaultCh; @prefix <- prefixCh) {
// ====================================
// =================== UTILS / INTERNAL
// ====================================
// validate string, used for purse ID, box ID, contract ID
for (@(str, ret) <= validateStringCh) {
match (
str,
Set("a","b","c","d","e","f","g","h","i","j","k","l","m","n","o","p","q","r","s","t","u","v","w","x","y", "z", "0", "1", "2", "3", "4", "5", "6", "7", "8", "9")
) {
(String, az09) => {
match (str.length() > 0,str.length() < 25) {
(true, true) => {
new tmpCh, itCh in {
for (@i <= itCh) {
if (i == str.length()) { @ret!(true) }
else {
if (az09.contains(str.slice(i, i + 1)) == true) { itCh!(i + 1) }
else { @ret!(false) }
}
} |
itCh!(0)
}
}
_ => {
@ret!(false)
}
}
}
_ => { @ret!(false) }
}
} |
// returns the box if it exists
for (@(boxId, return) <= getBoxCh) {
new ch1 in {
TreeHashMap!("get", boxesThm, boxId, *ch1) |
for (@exists <- ch1) {
if (exists == "exists") {
for (@box <<- @(*self, "boxes", boxId)) {
@return!(box)
}
} else {
@return!(Nil)
}
}
}
} |
// return the purse that has at least
// quantity in box if such exists
for (@(box, contractId, quantity, return) <= getPurseWithAtLeastQuantityCh) {
if (box.get(contractId) != Nil) {
new itCh, ch1, ch2 in {
getContractPursesThmCh!((contractId, *ch2)) |
for (@pursesThm <- ch2) {
for (ids <= itCh) {
match *ids {
Set() => { @return!(Nil) }
Set(last) => {
TreeHashMap!("get", pursesThm, last, *ch1) |
for (@purse <- ch1) {
if (purse.get("quantity") >= quantity) {
@return!(purse)
} else {
@return!(Nil)
}
}
}
Set(first ... rest) => {
TreeHashMap!("get", pursesThm, first, *ch1) |
for (@purse <- ch1) {
if (purse.get("quantity") >= quantity) {
@return!(purse)
} else {
itCh!(rest)
}
}
}
}
} |
itCh!(box.get(contractId))
}
}
} else {
@return!(Nil)
}
} |
// returns the purse if exists AND is associated with box
for (@(box, contractId, purseId, return) <= getPurseCh) {
new ch1 in {
if (box.get(contractId) == Nil) {
@return!(Nil)
} else {
if (box.get(contractId).contains(purseId) == true) {
getContractPursesThmCh!((contractId, *ch1)) |
for (@pursesThm <- ch1) {
TreeHashMap!("get", pursesThm, purseId, return)
}
} else {
@return!(Nil)
}
}
}
} |
// returns the tree hash map of the contract's purses if exists
for (@(contractId, return) <= getContractPursesThmCh) {
new ch1 in {
TreeHashMap!("get", contractsThm, contractId, *ch1) |
for (@exists <- ch1) {
if (exists == "exists") {
for (@pursesThm <<- @(*self, "purses", contractId)) {
@return!(pursesThm)
}
} else {
@return!(Nil)
}
}
}
} |
// returns the tree hash map of the contract's purses data if exists
for (@(contractId, return) <= getContractPursesDataThmCh) {
new ch1 in {
TreeHashMap!("get", contractsThm, contractId, *ch1) |
for (@exists <- ch1) {
if (exists == "exists") {
for (@pursesDataThm <<- @(*self, "pursesData", contractId)) {
@return!(pursesDataThm)
}
} else {
@return!(Nil)
}
}
}
} |
// remove purse in box, if found
for (@(boxId, contractId, purseId, return) <= removePurseInBoxCh) {
for (@box <- @(*self, "boxes", boxId)) {
if (box.get(contractId) == Nil) {
@return!("error: CRITICAL contract id not found in box") |
@(*self, "boxes", boxId)!(box)
} else {
if (box.get(contractId).contains(purseId) == false) {
@return!("error: CRITICAL purse does not exists in box") |
@(*self, "boxes", boxId)!(box)
} else {
stdout!(contractId ++ "/" ++ boxId ++ " purse " ++ purseId ++ " removed from box") |
@(*self, "boxes", boxId)!(box.set(contractId, box.get(contractId).delete(purseId))) |
@return!((true, Nil))
}
}
}
} |
// save purse id in box
for (@(contractId, purse, merge, return) <= savePurseInBoxCh) {
new ch1, ch3, iterateAndMergePursesCh in {
for (@box <- @(*self, "boxes", purse.get("boxId"))) {
getContractPursesThmCh!((contractId, *ch1)) |
for (@pursesThm <- ch1) {
if (pursesThm != Nil) {
if (box.get(contractId) == Nil) {
stdout!(contractId ++ "/" ++ purse.get("boxId") ++ " purse " ++ purse.get("id") ++ " saved to box") |
@(*self, "boxes", purse.get("boxId"))!(box.set(contractId, Set(purse.get("id")))) |
@return!((true, purse))
} else {
if (box.get(contractId).contains(purse.get("id")) == false) {
for (@contractConfig <<- @(*self, "contractConfig", contractId)) {
match (contractConfig.get("fungible") == true, merge) {
(true, true) => {
for (@pursesThm <<- @(*self, "purses", contractId)) {
TreeHashMap!("get", pursesThm, purse.get("id"), *ch3) |
for (@purse <- ch3) {
iterateAndMergePursesCh!((box, pursesThm))
}
}
}
_ => {
stdout!(contractId ++ "/" ++ purse.get("boxId") ++ " purse " ++ purse.get("id") ++ " saved to box") |
@(*self, "boxes", purse.get("boxId"))!(box.set(
contractId,
box.get(contractId).union(Set(purse.get("id")))
)) |
@return!((true, purse))
}
}
}
} else {
@(*self, "boxes", purse.get("boxId"))!(box) |
@return!("error: CRITICAL, purse already exists in box")
}
}
} else {
@(*self, "boxes", purse.get("boxId"))!(box) |
@return!("error: CRITICAL, pursesThm not found")
}
}
} |
// if contract is fungible, we may find a
// purse with same .price property
// if found, then merge and delete current purse
for (@(box, pursesThm) <- iterateAndMergePursesCh) {
new tmpCh, itCh in {
for (ids <= itCh) {
match *ids {
Set() => {
stdout!(contractId ++ "/" ++ purse.get("boxId") ++ " purse " ++ purse.get("id") ++ " saved to box") |
@(*self, "boxes", purse.get("boxId"))!(box.set(contractId, Set(purse.get("id")))) |
@return!((true, purse))
}
Set(last) => {
new ch4, ch5, ch6, ch7 in {
TreeHashMap!("get", pursesThm, last, *ch4) |
for (@purse2 <- ch4) {
match purse2.get("price") == purse.get("price") {
true => {
TreeHashMap!(
"set",
pursesThm,
last,
purse2.set("quantity", purse2.get("quantity") + purse.get("quantity")),
*ch5
) |
TreeHashMap!(
"set",
pursesThm,
purse.get("id"),
Nil,
*ch6
) |
for (@pursesDataThm <<- @(*self, "pursesData", contractId)) {
TreeHashMap!(
"set",
pursesDataThm,
purse.get("id"),
Nil,
*ch7
)
} |
for (_ <- ch5; _ <- ch6; _ <- ch7) {
stdout!(contractId ++ "/" ++ purse.get("boxId") ++ " purse " ++ purse.get("id") ++ " merged into purse " ++ purse2.get("id")) |
@return!((true, purse)) |
@(*self, "boxes", purse.get("boxId"))!(box)
}
}
_ => {
stdout!(contractId ++ "/" ++ purse.get("boxId") ++ " purse " ++ purse.get("id") ++ " saved to box") |
@(*self, "boxes", purse.get("boxId"))!(box.set(
contractId,
box.get(contractId).union(Set(purse.get("id")))
)) |
@return!((true, purse))
}
}
}
}
}
Set(first ... rest) => {
new ch4, ch5, ch6, ch7 in {
TreeHashMap!("get", pursesThm, first, *ch4) |
for (@purse2 <- ch4) {
match purse2.get("price") == purse.get("price") {
true => {
TreeHashMap!(
"set",
pursesThm,
first,
purse2.set("quantity", purse2.get("quantity") + purse.get("quantity")),
*ch5
) |
TreeHashMap!(
"set",
pursesThm,
purse.get("id"),
Nil,
*ch6
) |
for (@pursesDataThm <<- @(*self, "pursesData", contractId)) {
TreeHashMap!(
"set",
pursesDataThm,
purse.get("id"),
Nil,
*ch7
)
} |
for (_ <- ch5; _ <- ch6; _ <- ch7) {
stdout!(contractId ++ "/" ++ purse.get("boxId") ++ " purse " ++ purse.get("id") ++ " merged into purse " ++ purse2.get("id")) |
@return!((true, purse)) |
@(*self, "boxes", purse.get("boxId"))!(box)
}
}
_ => {
itCh!(rest)
}
}
}
}
}
}
} |
itCh!(box.get(contractId))
}
}
}
} |
/*
makePurseCh
only place where new purses are created:
SWAP, WITHDRAW, and CREATE_PURSE may call this channel
depending on if .fungible is true or false, it decides
which id to give to the new purse, then it creates the
purse and saves to box
*/
for (@(contractId, properties, data, merge, return) <= makePurseCh) {
new ch1, ch2, ch3, ch4, idAndQuantityCh in {
for (@contractConfig <<- @(*self, "contractConfig", contractId)) {
if (contractConfig.get("fungible") == true) {
for (_ <- @(*self, "contractConfig", contractId)) {
@(*self, "contractConfig", contractId)!(contractConfig.set("counter", contractConfig.get("counter") + 1))
} |
idAndQuantityCh!({ "id": "\${n}" %% { "n": contractConfig.get("counter") }, "quantity": properties.get("quantity") })
} else {
for (@pursesThm <<- @(*self, "purses", contractId)) {
TreeHashMap!("get", pursesThm, properties.get("id"), *ch1) |
for (@existingPurse <- ch1) {
// check that nft does not exist
if (existingPurse == Nil) {
if (properties.get("id") == "0") {
idAndQuantityCh!({ "id": properties.get("id"), "quantity": properties.get("quantity") })
} else {
idAndQuantityCh!({ "id": properties.get("id"), "quantity": 1 })
}
} else {
// nft with id: "0" is a special nft from which
// anyone can mint a nft that does not exist yet
// used by dappy name system for example
if (properties.get("id") == "0") {
TreeHashMap!("get", pursesThm, properties.get("newId"), *ch2) |
for (@purseWithNewId <- ch2) {
match (properties.get("newId"), purseWithNewId) {
(String, Nil) => {
idAndQuantityCh!({ "id": properties.get("newId"), "quantity": 1 })
}
_ => {
@return!("error: no .newId in payload or .newId already exists")
}
}
}
} else {
@return!("error: purse id already exists")
}
}
}
}
}
} |
for (@idAndQuantity <- idAndQuantityCh) {
match properties
.set("id", idAndQuantity.get("id"))
.set("quantity", idAndQuantity.get("quantity"))
.delete("newId")
{
purse => {
match purse {
{
"quantity": Int,
"timestamp": Int,
"boxId": String,
"id": String,
"price": (String, Int) \\/ (String, String) \\/ Nil
} => {
for (@pursesDataThm <<- @(*self, "pursesData", contractId)) {
for (@pursesThm <<- @(*self, "purses", contractId)) {
TreeHashMap!("set", pursesThm, purse.get("id"), purse, *ch3) |
TreeHashMap!("set", pursesDataThm, purse.get("id"), data, *ch4)
}
} |
for (_ <- ch3; _ <- ch4) {
if (purse.get("boxId") == "_burn") {
stdout!(contractId ++ " purse " ++ purse.get("id") ++ " burned") |
@return!((true, purse))
} else {
savePurseInBoxCh!((contractId, purse, merge, return))
}
}
}
_ => {
@return!("error: invalid purse, one of the following errors: id length must be between length 1 and 24")
}
}
}
}
}
}
} |
// ====================================
// ===== ANY USER / PUBLIC capabilities
// ====================================
for (@("PUBLIC_READ_PURSES_AT_INDEX", contractId, i, return) <= entryCh) {
new ch1 in {
getContractPursesThmCh!((contractId, *ch1)) |
for (@pursesThm <- ch1) {
if (pursesThm == Nil) {
@return!("error: contract not found")
} else {
TreeHashMap!("getValuesAtIndex", pursesThm, i, return)
}
}
}
} |
for (@("PUBLIC_READ_CONFIG", contractId, return) <= entryCh) {
for (@config <<- @(*self, "contractConfig", contractId)) {
@return!(config)
}
} |
for (@("PUBLIC_READ_BOX", boxId, return) <= entryCh) {
new ch1 in {
getBoxCh!((boxId, *ch1)) |
for (@box <- ch1) {
if (box == Nil) {
@return!("error: box not found")
} else {
for (@superKeys <<- @(*self, "boxesSuperKeys", boxId)) {
for (@config <<- @(*self, "boxConfig", boxId)) {
@return!(config.union({ "superKeys": superKeys, "purses": box, "version": "16.0.0" }))
}
}
}
}
}
} |
for (@("PUBLIC_READ_LOGS", contractId, return) <= entryCh) {
new ch1 in {
getContractPursesThmCh!((contractId, *ch1)) |
for (@pursesThm <- ch1) {
if (pursesThm == Nil) {
@return!("error: contract not found")
} else {
for (@logs <<- @(*self, "LOGS", contractId)) {
@return!((true, logs))
}
}
}
}
} |
for (@("PUBLIC_READ_PURSE", payload, return) <= entryCh) {
new ch1 in {
getContractPursesThmCh!((payload.get("contractId"), *ch1)) |
for (@pursesThm <- ch1) {
if (pursesThm == Nil) {
@return!("error: contract not found")
} else {
match payload.get("purseId") {
String => {
TreeHashMap!("get", pursesThm, payload.get("purseId"), return)
}
_ => {
@return!("error: payload.purseId must be a string")
}
}
}
}
}
} |
for (@("PUBLIC_READ_PURSE_DATA", payload, return) <= entryCh) {
new ch1 in {
getContractPursesDataThmCh!((payload.get("contractId"), *ch1)) |
for (@pursesDataThm <- ch1) {
if (pursesDataThm == Nil) {
@return!("error: contract not found")
} else {
match payload.get("purseId") {
String => {
TreeHashMap!("get", pursesDataThm, payload.get("purseId"), return)
}
_ => {
@return!("error: payload.purseId must be a string")
}
}
}
}
}
} |
for (@("PUBLIC_DELETE_EXPIRED_PURSE", contractId, boxId, purseId, return) <= entryCh) {
new ch1, ch2, ch3, ch4, ch5, ch6, ch7, ch8, ch9, proceeedDeleteCh, unlock in {
for (@result <- unlock) {
@(*self, "BOX_LOCK", boxId)!(Nil) |
@(*self, "CONTRACT_LOCK", contractId)!(Nil) |
@return!(result)
} |
getContractPursesThmCh!((contractId, *ch1)) |
getBoxCh!((boxId, *ch2)) |
for (@pursesThm <- ch1; @box <- ch2) {
if (pursesThm != Nil and box != Nil) {
for (
_ <- @(*self, "CONTRACT_LOCK", contractId);
_ <- @(*self, "BOX_LOCK", boxId)
) {
proceeedDeleteCh!(Nil)
}
} else {
@return!("error: box or contract not found")
}
} |
// RACE SAFE / RESOURCES LOCKED
// after lock of 1 contract and 1 box
for (_ <- proceeedDeleteCh) {
getContractPursesThmCh!((contractId, *ch3)) |
getContractPursesDataThmCh!((contractId, *ch4)) |
for (@pursesThm <- ch3; @pursesDataThm <- ch4) {
TreeHashMap!("get", pursesThm, purseId, *ch5) |
for (@purse <- ch5) {
if (purse != Nil) {
if (purse.get("boxId") == boxId) {
for (@config <<- @(*self, "contractConfig", contractId)) {
match (config.get("fungible"), purseId == "0", config.get("expires")) {
(false, false, Int) => {
blockData!(*ch6) |
for (_, @timestamp, _ <- ch6) {
if (timestamp - purse.get("timestamp") > config.get("expires")) {
TreeHashMap!("set", pursesThm, purse.get("id"), Nil, *ch7) |
TreeHashMap!("set", pursesDataThm, purse.get("id"), Nil, *ch8) |
removePurseInBoxCh!((purse.get("boxId"), contractId, purse.get("id"), *ch9)) |
for (_ <- ch7; _ <- ch8; _ <- ch9) {
unlock!((true, Nil))
}
} else {
unlock!("error: purse has not expired")
}
}
}
_ => {
unlock!("error: cannot delete")
}
}
}
} else {
unlock!("error: invalid box id")
}
} else {
unlock!("error: purse not found")
}
}
}
}
}
} |
for (@("PUBLIC_REGISTER_BOX", payload, return) <= entryCh) {
match (payload.get("boxId"), payload.get("revAddress"), payload.get("publicKey")) {
(String, String, String) => {
new ch1, ch2, ch3, ch4, ch5, ch6, ch7, registerBoxUnlock in {
for (@result <- registerBoxUnlock) {
@(*self, "REGISTER_BOX_LOCK")!(Nil) |
@return!(result)
} |
// Verify that payload.revAddress is a real one with at
// least 1 dust
@RevVault!("findOrCreate", payload.get("revAddress"), *ch4) |
for (@(true, revVault) <- ch4) {
@revVault!("balance", *ch5) |
for (@balance <- ch5) {
match balance {
Int => {
if (balance == 0) {
@return!("error: REV address must have at least 1 dust")
} else {
validateStringCh!((payload.get("boxId"), *ch7)) |
for (@valid <- ch7) {
if (valid == true) {
match prefix ++ payload.get("boxId") {
boxId => {
for (_ <- @(*self, "REGISTER_BOX_LOCK")) {
ch6!(boxId) |
getBoxCh!((boxId, *ch1))
}
}
}
} else {
@return!("error: invalid box id")
}
}
}
}
_ => {
@return!("error: REV address must have at least 1 dust")
}
}
}
} |
for (@existingBox <- ch1; @boxId <- ch6) {
if (existingBox == Nil) {
new boxCh in {
TreeHashMap!("set", boxesThm, boxId, "exists", *ch2) |
for (_ <- ch2) {
@(*self, "boxes", boxId)!({}) |
@(*self, "boxesSuperKeys", boxId)!(Set()) |
@(*self, "boxConfig", boxId)!({ "publicKey": payload.get("publicKey"), "revAddress": payload.get("revAddress") }) |
registerBoxUnlock!((true, { "boxId": boxId, "boxCh": bundle+{*boxCh} })) |
initLocksForBoxCh!(boxId) |
initializeOCAPOnBoxCh!((*boxCh, boxId))
}
}
} else {
registerBoxUnlock!("error: box already exists")
}
}
}
}
}
} |
for (@(boxCh, boxId) <= initializeOCAPOnBoxCh) {
for (@("REGISTER_CONTRACT", payload, return) <= @boxCh) {
new registerContract, ch1, ch2, ch3, ch4, ch5, ch6, registerUnlock in {
for (@result <- registerUnlock) {
@(*self, "REGISTER_CONTRACT_LOCK")!(Nil) |
@return!(result)
} |
match payload {
{ "contractId": String, "fungible": Bool, "expires": Nil \\/ Int } => {
for (_ <- @(*self, "REGISTER_CONTRACT_LOCK")) {
validateStringCh!((payload.get("contractId"), *ch6)) |
for (@valid <- ch6) {
if (valid == true) {
if (payload.get("expires") == Nil) {
registerContract!(prefix ++ payload.get("contractId"))
} else {
// minimum 2 hours expiration
if (payload.get("expires") >= 1000 * 60 * 60 * 2) {
registerContract!(prefix ++ payload.get("contractId"))
} else {
registerUnlock!("error: .expires must be at least 2 hours")
}
}
} else {
registerUnlock!("error: invalid contract id")