diff --git a/.gitignore b/.gitignore index fc3b44e..39f0dad 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,8 @@ build *.abi *.wasm +!tests/*.wasm +!tests/*.abi node_modules .idea/ .vscode/ diff --git a/Makefile b/Makefile index a01644a..bebf97e 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,4 @@ -EOS_CC ?= eosio-cpp -ABI_CC ?= eosio-abigen +EOS_CC ?= cdt-cpp SKIP_CONTRACTS := $(wildcard contracts/swap/*.cpp contracts/taskproxy/*.cpp) @@ -7,16 +6,11 @@ SRC = $(filter-out $(SKIP_CONTRACTS), $(wildcard contracts/*/*.cpp)) WASM = $(SRC:.cpp=.wasm) ABI = $(WASM:.wasm=.abi) -all: $(WASM) $(ABI) +all: $(WASM) -%.wasm: %.cpp %.hpp $(%-shared.hpp) +%.abi %.wasm: %.cpp %.hpp $(%-shared.hpp) $(EOS_CC) -o $@ $< -%.abi: %.cpp - $(ABI_CC) -contract=$(basename $(^F)) -output $@ $^ - -.PHONY: serve-docs clean - clean: rm -f $(WASM) $(ABI) @@ -25,3 +19,11 @@ serve-docs: build-docs: jekyll b -s docs + +test-contracts: + npm run lumo e2e.force + +deploy-testnet: + bb deploy jungle4 + +.PHONY: serve-docs clean test-contracts deploy-testnet diff --git a/bb.edn b/bb.edn index 277016c..2fb90d6 100644 --- a/bb.edn +++ b/bb.edn @@ -6,4 +6,5 @@ init effect/init process-cycle effect/process-cycle deploy-mainnet effect/msig-deploy - atp effect/make-atp}} + atp effect/make-atp + create-n-cycles effect/create-n-cycles}} diff --git a/contracts/README.md b/contracts/README.md index 3292466..ffb714c 100644 --- a/contracts/README.md +++ b/contracts/README.md @@ -20,7 +20,8 @@ It's possible to use a docker container instead of installing **eosio.cdt** locally: ```bash -EOS_CC="docker run --rm -it -v $(pwd):/app -w /app effectai/eosio-cdt:v1.5.0 eosio-cpp" ABI_CC="docker run --rm -it -v $(pwd):/app -w /app effectai/eosio-cdt:v1.5.0 eosio-abigen" make all +export EOS_CC="sudo docker run --rm -v $(pwd):/build -w /build antelope/cdt:3.1.0 cdt-cpp" +make all ``` ## 🚚 Deploying diff --git a/contracts/force/atomicdata.hpp b/contracts/force/atomicdata.hpp new file mode 100644 index 0000000..0a5bb36 --- /dev/null +++ b/contracts/force/atomicdata.hpp @@ -0,0 +1,519 @@ +#pragma once + +#include +#include "base58.hpp" + +using namespace eosio; +using namespace std; + +namespace atomicdata { + + //Custom vector types need to be defined because otherwise a bug in the ABI serialization + //would cause the ABI to be invalid + typedef std::vector INT8_VEC; + typedef std::vector INT16_VEC; + typedef std::vector INT32_VEC; + typedef std::vector INT64_VEC; + typedef std::vector UINT8_VEC; + typedef std::vector UINT16_VEC; + typedef std::vector UINT32_VEC; + typedef std::vector UINT64_VEC; + typedef std::vector FLOAT_VEC; + typedef std::vector DOUBLE_VEC; + typedef std::vector STRING_VEC; + + typedef std::variant <\ + int8_t, int16_t, int32_t, int64_t, \ + uint8_t, uint16_t, uint32_t, uint64_t, \ + float, double, std::string, \ + atomicdata::INT8_VEC, atomicdata::INT16_VEC, atomicdata::INT32_VEC, atomicdata::INT64_VEC, \ + atomicdata::UINT8_VEC, atomicdata::UINT16_VEC, atomicdata::UINT32_VEC, atomicdata::UINT64_VEC, \ + atomicdata::FLOAT_VEC, atomicdata::DOUBLE_VEC, atomicdata::STRING_VEC> ATOMIC_ATTRIBUTE; + + typedef std::map ATTRIBUTE_MAP; + + struct FORMAT { + std::string name; + std::string type; + }; + + static constexpr uint64_t RESERVED = 4; + + + vector toVarintBytes(uint64_t number, uint64_t original_bytes = 8) { + if (original_bytes < 8) { + uint64_t bitmask = ((uint64_t) 1 << original_bytes * 8) - 1; + number &= bitmask; + } + + vector bytes = {}; + while (number >= 128) { + // sets msb, stores remainder in lower bits + bytes.push_back((uint8_t)(128 + number % 128)); + number /= 128; + } + bytes.push_back((uint8_t) number); + + return bytes; + } + + uint64_t unsignedFromVarintBytes(vector ::iterator &itr) { + uint64_t number = 0; + uint64_t multiplier = 1; + + while (*itr >= 128) { + number += (((uint64_t) * itr) - 128) * multiplier; + itr++; + multiplier *= 128; + } + number += ((uint64_t) * itr) * multiplier; + itr++; + + return number; + } + + //It is expected that the number is smaller than 2^byte_amount + vector toIntBytes(uint64_t number, uint64_t byte_amount) { + vector bytes = {}; + for (uint64_t i = 0; i < byte_amount; i++) { + bytes.push_back((uint8_t) number % 256); + number /= 256; + } + return bytes; + } + + uint64_t unsignedFromIntBytes(vector ::iterator &itr, uint64_t original_bytes = 8) { + uint64_t number = 0; + uint64_t multiplier = 1; + + for (uint64_t i = 0; i < original_bytes; i++) { + number += ((uint64_t) * itr) * multiplier; + multiplier *= 256; + itr++; + } + + return number; + } + + + uint64_t zigzagEncode(int64_t value) { + if (value < 0) { + return (uint64_t)(-1 * (value + 1)) * 2 + 1; + } else { + return (uint64_t) value * 2; + } + } + + int64_t zigzagDecode(uint64_t value) { + if (value % 2 == 0) { + return (int64_t)(value / 2); + } else { + return (int64_t)(value / 2) * -1 - 1; + } + } + + + vector serialize_attribute(const string &type, const ATOMIC_ATTRIBUTE &attr) { + if (type.find("[]", type.length() - 2) == type.length() - 2) { + //Type is an array + string base_type = type.substr(0, type.length() - 2); + + if (std::holds_alternative (attr)) { + INT8_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + INT16_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + INT32_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + INT64_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + UINT8_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + UINT16_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + UINT32_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + UINT64_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + FLOAT_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + DOUBLE_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } else if (std::holds_alternative (attr)) { + STRING_VEC vec = std::get (attr); + vector serialized_data = toVarintBytes(vec.size()); + for (auto child : vec) { + ATOMIC_ATTRIBUTE child_attr = child; + vector serialized_element = serialize_attribute(base_type, child_attr); + serialized_data.insert(serialized_data.end(), serialized_element.begin(), serialized_element.end()); + } + return serialized_data; + + } + } + + if (type == "int8") { + check(std::holds_alternative (attr), "Expected a int8, but got something else"); + return toVarintBytes(zigzagEncode(std::get (attr)), 1); + } else if (type == "int16") { + check(std::holds_alternative (attr), "Expected a int16, but got something else"); + return toVarintBytes(zigzagEncode(std::get (attr)), 2); + } else if (type == "int32") { + check(std::holds_alternative (attr), "Expected a int32, but got something else"); + return toVarintBytes(zigzagEncode(std::get (attr)), 4); + } else if (type == "int64") { + check(std::holds_alternative (attr), "Expected a int64, but got something else"); + return toVarintBytes(zigzagEncode(std::get (attr)), 8); + + } else if (type == "uint8") { + check(std::holds_alternative (attr), "Expected a uint8, but got something else"); + return toVarintBytes(std::get (attr), 1); + } else if (type == "uint16") { + check(std::holds_alternative (attr), "Expected a uint16, but got something else"); + return toVarintBytes(std::get (attr), 2); + } else if (type == "uint32") { + check(std::holds_alternative (attr), "Expected a uint32, but got something else"); + return toVarintBytes(std::get (attr), 4); + } else if (type == "uint64") { + check(std::holds_alternative (attr), "Expected a uint64, but got something else"); + return toVarintBytes(std::get (attr), 8); + + } else if (type == "fixed8" || type == "byte") { + check(std::holds_alternative (attr), "Expected a uint8 (fixed8 / byte), but got something else"); + return toIntBytes(std::get (attr), 1); + } else if (type == "fixed16") { + check(std::holds_alternative (attr), "Expected a uint16 (fixed16), but got something else"); + return toIntBytes(std::get (attr), 2); + } else if (type == "fixed32") { + check(std::holds_alternative (attr), "Expected a uint32 (fixed32), but got something else"); + return toIntBytes(std::get (attr), 4); + } else if (type == "fixed64") { + check(std::holds_alternative (attr), "Expected a uint64 (fixed64), but got something else"); + return toIntBytes(std::get (attr), 8); + + } else if (type == "float") { + check(std::holds_alternative (attr), "Expected a float, but got something else"); + float float_value = std::get (attr); + auto *byte_value = reinterpret_cast(&float_value); + vector serialized_data = {}; + serialized_data.reserve(4); + for (int i = 0; i < 4; i++) { + serialized_data.push_back(*(byte_value + i)); + } + return serialized_data; + + } else if (type == "double") { + check(std::holds_alternative (attr), "Expected a double, but got something else"); + double float_value = std::get (attr); + auto *byte_value = reinterpret_cast(&float_value); + vector serialized_data = {}; + serialized_data.reserve(8); + for (int i = 0; i < 8; i++) { + serialized_data.push_back(*(byte_value + i)); + } + return serialized_data; + + } else if (type == "string" || type == "image") { + check(std::holds_alternative (attr), "Expected a string, but got something else"); + string text = std::get (attr); + vector serialized_data(text.begin(), text.end()); + + vector length_bytes = toVarintBytes(text.length()); + serialized_data.insert(serialized_data.begin(), length_bytes.begin(), length_bytes.end()); + return serialized_data; + + } else if (type == "ipfs") { + check(std::holds_alternative (attr), "Expected a string (ipfs), but got something else"); + vector result = {}; + check(DecodeBase58(std::get (attr), result), + "Error when decoding IPFS string"); + vector length_bytes = toVarintBytes(result.size()); + result.insert(result.begin(), length_bytes.begin(), length_bytes.end()); + return result; + + } else if (type == "bool") { + check(std::holds_alternative (attr), + "Expected a bool (needs to be provided as uint8_t because of C++ restrictions), but got something else"); + uint8_t value = std::get (attr); + check(value == 0 || value == 1, + "Bools need to be provided as an uin8_t that is either 0 or 1"); + return {value}; + + } else { + check(false, "No type could be matched - " + type); + return {}; //This point can never be reached because the check above will always throw. + //Just to silence the compiler warning + } + } + + + ATOMIC_ATTRIBUTE deserialize_attribute(const string &type, vector ::iterator &itr) { + if (type.find("[]", type.length() - 2) == type.length() - 2) { + //Type is an array + uint64_t array_length = unsignedFromVarintBytes(itr); + string base_type = type.substr(0, type.length() - 2); + + if (type == "int8[]") { + INT8_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + } else if (type == "int16[]") { + INT16_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + } else if (type == "int32[]") { + INT32_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + } else if (type == "int64[]") { + INT64_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + + } else if (type == "uint8[]" || type == "fixed8[]" || type == "bool[]") { + UINT8_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + } else if (type == "uint16[]" || type == "fixed16[]") { + UINT16_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + } else if (type == "uint32[]" || type == "fixed32[]") { + UINT32_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + } else if (type == "uint64[]" || type == "fixed64[]") { + UINT64_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + + } else if (type == "float[]") { + FLOAT_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + + } else if (type == "double[]") { + DOUBLE_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + + } else if (type == "string[]" || type == "image[]") { + STRING_VEC vec = {}; + for (uint64_t i = 0; i < array_length; i++) { + vec.push_back(std::get (deserialize_attribute(base_type, itr))); + } + return vec; + + } + } + + if (type == "int8") { + return (int8_t) zigzagDecode(unsignedFromVarintBytes(itr)); + } else if (type == "int16") { + return (int16_t) zigzagDecode(unsignedFromVarintBytes(itr)); + } else if (type == "int32") { + return (int32_t) zigzagDecode(unsignedFromVarintBytes(itr)); + } else if (type == "int64") { + return (int64_t) zigzagDecode(unsignedFromVarintBytes(itr)); + + } else if (type == "uint8") { + return (uint8_t) unsignedFromVarintBytes(itr); + } else if (type == "uint16") { + return (uint16_t) unsignedFromVarintBytes(itr); + } else if (type == "uint32") { + return (uint32_t) unsignedFromVarintBytes(itr); + } else if (type == "uint64") { + return (uint64_t) unsignedFromVarintBytes(itr); + + } else if (type == "fixed8") { + return (uint8_t) unsignedFromIntBytes(itr, 1); + } else if (type == "fixed16") { + return (uint16_t) unsignedFromIntBytes(itr, 2); + } else if (type == "fixed32") { + return (uint32_t) unsignedFromIntBytes(itr, 4); + } else if (type == "fixed64") { + return (uint64_t) unsignedFromIntBytes(itr, 8); + + } else if (type == "float") { + uint8_t array_repr[4]; + for (uint8_t &i : array_repr) { + i = *itr; + itr++; + } + auto *val = reinterpret_cast(&array_repr); + return *val; + + } else if (type == "double") { + uint8_t array_repr[8]; + for (uint8_t &i : array_repr) { + i = *itr; + itr++; + } + auto *val = reinterpret_cast(&array_repr); + return *val; + + } else if (type == "string" || type == "image") { + uint64_t string_length = unsignedFromVarintBytes(itr); + string text(itr, itr + string_length); + + itr += string_length; + return text; + + } else if (type == "ipfs") { + uint64_t array_length = unsignedFromVarintBytes(itr); + vector byte_array = {}; + byte_array.insert(byte_array.begin(), itr, itr + array_length); + + itr += array_length; + return EncodeBase58(byte_array); + + } else if (type == "bool" || type == "byte") { + uint8_t next_byte = *itr; + itr++; + return next_byte; + + } else { + check(false, "No type could be matched - " + type); + return ""; //This point can never be reached because the check above will always throw. + //Just to silence the compiler warning + } + } + + + vector serialize(ATTRIBUTE_MAP attr_map, const vector &format_lines) { + uint64_t number = 0; + vector serialized_data = {}; + for (atomicassets::FORMAT line : format_lines) { + auto attribute_itr = attr_map.find(line.name); + if (attribute_itr != attr_map.end()) { + const vector &identifier = toVarintBytes(number + RESERVED); + serialized_data.insert(serialized_data.end(), identifier.begin(), identifier.end()); + + const vector &child_data = serialize_attribute(line.type, attribute_itr->second); + serialized_data.insert(serialized_data.end(), child_data.begin(), child_data.end()); + + attr_map.erase(attribute_itr); + } + number++; + } + if (attr_map.begin() != attr_map.end()) { + check(false, + "The following attribute could not be serialized, because it is not specified in the provided format: " + + attr_map.begin()->first); + } + return serialized_data; + } + + + ATTRIBUTE_MAP deserialize(const vector &data, const vector &format_lines) { + ATTRIBUTE_MAP attr_map = {}; + + auto itr = data.begin(); + while (itr != data.end()) { + uint64_t identifier = unsignedFromVarintBytes(itr); + atomicassets::FORMAT format = format_lines.at(identifier - RESERVED); + attr_map[format.name] = deserialize_attribute(format.type, itr); + } + + return attr_map; + } +} diff --git a/contracts/force/base58.hpp b/contracts/force/base58.hpp new file mode 100644 index 0000000..61fa552 --- /dev/null +++ b/contracts/force/base58.hpp @@ -0,0 +1,129 @@ +// Copyright (c) 2014-2019 The Bitcoin Core developers +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +//(Slightly modified for the needs of our eosio contract) + +#include +#include + +/** All alphanumeric characters except for "0", "I", "O", and "l" */ +static const char* pszBase58 = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"; +static const int8_t mapBase58[256] = { + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1, 0, 1, 2, 3, 4, 5, 6, 7, 8,-1,-1,-1,-1,-1,-1, + -1, 9,10,11,12,13,14,15, 16,-1,17,18,19,20,21,-1, + 22,23,24,25,26,27,28,29, 30,31,32,-1,-1,-1,-1,-1, + -1,33,34,35,36,37,38,39, 40,41,42,43,-1,44,45,46, + 47,48,49,50,51,52,53,54, 55,56,57,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, + -1,-1,-1,-1,-1,-1,-1,-1, -1,-1,-1,-1,-1,-1,-1,-1, +}; + + +std::string EncodeBase58(const unsigned char* pbegin, const unsigned char* pend) +{ + // Skip & count leading zeroes. + int zeroes = 0; + int length = 0; + while (pbegin != pend && *pbegin == 0) { + pbegin++; + zeroes++; + } + // Allocate enough space in big-endian base58 representation. + int size = (pend - pbegin) * 138 / 100 + 1; // log(256) / log(58), rounded up. + std::vector b58(size); + // Process the bytes. + while (pbegin != pend) { + int carry = *pbegin; + int i = 0; + // Apply "b58 = b58 * 256 + ch". + for (std::vector::reverse_iterator it = b58.rbegin(); (carry != 0 || i < length) && (it != b58.rend()); it++, i++) { + carry += 256 * (*it); + *it = carry % 58; + carry /= 58; + } + + assert(carry == 0); + length = i; + pbegin++; + } + // Skip leading zeroes in base58 result. + std::vector::iterator it = b58.begin() + (size - length); + while (it != b58.end() && *it == 0) + it++; + // Translate the result into a string. + std::string str; + str.reserve(zeroes + (b58.end() - it)); + str.assign(zeroes, '1'); + while (it != b58.end()) + str += pszBase58[*(it++)]; + return str; +} + +std::string EncodeBase58(const std::vector& vch) +{ + return EncodeBase58(vch.data(), vch.data() + vch.size()); +} + + +//Removed the max return length. +bool DecodeBase58(const char* psz, std::vector& vch) +{ + // Skip leading spaces. + while (*psz && isspace(*psz)) + psz++; + // Skip and count leading '1's. + int zeroes = 0; + int length = 0; + while (*psz == '1') { + zeroes++; + psz++; + } + // Allocate enough space in big-endian base256 representation. + int size = strlen(psz) * 733 /1000 + 1; // log(58) / log(256), rounded up. + std::vector b256(size); + // Process the characters. + static_assert(sizeof(mapBase58)/sizeof(mapBase58[0]) == 256, "mapBase58.size() should be 256"); // guarantee not out of range + while (*psz && !isspace(*psz)) { + // Decode base58 character + int carry = mapBase58[(uint8_t)*psz]; + if (carry == -1) // Invalid b58 character + return false; + int i = 0; + for (std::vector::reverse_iterator it = b256.rbegin(); (carry != 0 || i < length) && (it != b256.rend()); ++it, ++i) { + carry += 58 * (*it); + *it = carry % 256; + carry /= 256; + } + assert(carry == 0); + length = i; + psz++; + } + // Skip trailing spaces. + while (isspace(*psz)) + psz++; + if (*psz != 0) + return false; + // Skip leading zeroes in b256. + std::vector::iterator it = b256.begin() + (size - length); + // Copy result into output vector. + vch.reserve(zeroes + (b256.end() - it)); + vch.assign(zeroes, 0x00); + while (it != b256.end()) + vch.push_back(*(it++)); + return true; +} + +bool DecodeBase58(const std::string& str, std::vector& vchRet) +{ + return DecodeBase58(str.c_str(), vchRet); +} diff --git a/contracts/force/force.cpp b/contracts/force/force.cpp index e815c86..0de85cb 100644 --- a/contracts/force/force.cpp +++ b/contracts/force/force.cpp @@ -1,43 +1,65 @@ #include "force.hpp" -void force::init(eosio::name vaccount_contract, uint32_t force_vaccount_id, - uint32_t payout_delay_sec, uint32_t release_task_delay_sec) { +void force::init(eosio::name vaccount_contract, + uint32_t force_vaccount_id, + uint32_t payout_delay_sec, + uint32_t release_task_delay_sec, + eosio::name fee_contract, + float fee_percentage) { eosio::require_auth(_self); - _config.set(config{vaccount_contract, - force_vaccount_id, - payout_delay_sec, - release_task_delay_sec}, _self); + auto settings = _settings.find(settings_pk.value); + // _settings.erase(settings); + _settings.emplace(_self, + [&](auto& s) + { + s.vaccount_contract = vaccount_contract; + s.force_vaccount_id = force_vaccount_id; + s.payout_delay_sec = payout_delay_sec; + s.release_task_delay_sec = release_task_delay_sec; + s.fee_contract = fee_contract; + s.fee_percentage = fee_percentage; + }); } -void force::mkcampaign(vaccount::vaddress owner, content content, eosio::extended_asset reward, - camp_quali_map qualis, eosio::name payer, vaccount::sig sig) { +void force::mkcampaign(vaccount::vaddress owner, + content content, + uint32_t max_task_time, + eosio::extended_asset reward, + std::vector qualis, + eosio::name payer) { campaign_table camp_tbl(_self, _self.value); uint32_t camp_id = camp_tbl.available_primary_key(); - // TODO: add owner, reward, and qualis to the params - mkcampaign_params params = {9, content}; - std::vector msg_bytes = pack(params); - vaccount::require_auth(msg_bytes, owner, sig); + + vaccount::require_auth(std::vector(), owner, std::nullopt); camp_tbl.emplace(payer, [&](auto& c) { c.id = camp_id; + c.reservations_done = 0; + c.active_batch = 0; + c.num_batches = 0; c.content = content; c.owner = owner; + c.max_task_time = max_task_time; c.reward = reward; - c.qualis.emplace(qualis); + c.paused = false; + c.qualis = qualis; + c.total_tasks = 0; }); } -void force::editcampaign(uint32_t campaign_id, vaccount::vaddress owner, content content, - eosio::extended_asset reward, camp_quali_map qualis, - eosio::name payer, vaccount::sig sig) { +void force::editcampaign(uint32_t campaign_id, + vaccount::vaddress owner, + content content, + bool paused, + eosio::extended_asset reward, + std::vector qualis, + eosio::name payer) { campaign_table camp_tbl(_self, _self.value); auto& camp = camp_tbl.get(campaign_id, "campaign does not exist"); - editcampaign_params params = {10, campaign_id, content}; - std::vector msg_bytes = pack(params); - vaccount::require_auth(msg_bytes, owner, sig); + vaccount::require_auth(std::vector(), owner, std::nullopt); camp_tbl.modify(camp, payer, @@ -45,31 +67,31 @@ void force::editcampaign(uint32_t campaign_id, vaccount::vaddress owner, content { c.content = content; c.reward = reward; - c.qualis.emplace(qualis); + c.qualis = qualis; }); } -void force::rmcampaign(uint32_t campaign_id, vaccount::vaddress owner, vaccount::sig sig) { +void force::rmcampaign(uint32_t campaign_id, vaccount::vaddress owner) { campaign_table camp_tbl(_self, _self.value); auto camp_itr = camp_tbl.find(campaign_id); eosio::check(camp_itr != camp_tbl.end(), "campaign does not exist"); - rmcampaign_params params = {11, campaign_id}; - std::vector msg_bytes = pack(params); - vaccount::require_auth(msg_bytes, owner, sig); + vaccount::require_auth(std::vector(), owner, std::nullopt); camp_tbl.erase(camp_itr); } -void force::mkbatch(uint32_t id, uint32_t campaign_id, content content, - checksum256 task_merkle_root, uint32_t repetitions, - std::optional qualis, eosio::name payer, vaccount::sig sig) { +void force::mkbatch(uint32_t id, + uint32_t campaign_id, + content content, + uint32_t repetitions, + eosio::name payer) { campaign_table camp_tbl(_self, _self.value); - auto& camp = camp_tbl.get(campaign_id, "campaign not found"); + auto camp = camp_tbl.require_find(campaign_id, "campaign not found"); - mkbatch_params params = {8, id, campaign_id, content, task_merkle_root}; - std::vector msg_bytes = pack(params); - vaccount::require_auth(msg_bytes, camp.owner, sig); + eosio::check(id == camp->num_batches, "batch id must be sequential"); + + vaccount::require_auth(std::vector(), camp->owner, std::nullopt); eosio::check(repetitions < force::MAX_REPETITIONS, "too many repetitions"); batch_table batch_tbl(_self, _self.value); @@ -79,34 +101,51 @@ void force::mkbatch(uint32_t id, uint32_t campaign_id, content content, b.campaign_id = campaign_id; b.id = id; b.content = content; - b.task_merkle_root = task_merkle_root; - b.balance = {0, camp.reward.get_extended_symbol()}; + b.balance = {0, camp->reward.get_extended_symbol()}; b.repetitions = repetitions; - b.reward.emplace(camp.reward); + b.reward = camp->reward; b.num_tasks = 0; - if (qualis.has_value()) - b.qualis.emplace(qualis.value()); }); + camp_tbl.modify(camp, eosio::same_payer, [&](auto& c) { c.num_batches += 1; }); } -void force::rmbatch(uint32_t id, uint32_t campaign_id, vaccount::sig sig) { +void force::rmbatch(uint32_t id, uint32_t campaign_id) { batch_table batch_tbl(_self, _self.value); campaign_table camp_tbl(_self, _self.value); uint64_t batch_pk =(uint64_t{campaign_id} << 32) | id; - auto& camp = camp_tbl.get(campaign_id, "campaign not found"); + auto camp = camp_tbl.require_find(campaign_id, "campaign not found"); - auto batch_itr = batch_tbl.find(batch_pk); - eosio::check(batch_itr != batch_tbl.end(), "batch does not exist"); + auto batch = batch_tbl.require_find(batch_pk, "batch does not exist"); - rmbatch_params params = {12, id, campaign_id}; + vaccount::require_auth(std::vector(), camp->owner, std::nullopt); - std::vector msg_bytes = pack(params); - printhex(&msg_bytes[0], msg_bytes.size()); - vaccount::require_auth(msg_bytes, camp.owner, sig); + uint32_t batch_tasks_done = (camp->reservations_done - batch->start_task_idx); - batch_tbl.erase(batch_itr); + if (batch->id > camp->active_batch) { + // if the batch has not started, we should empty it, the row + // can only be erased when the campaign caught up + eosio::check(camp->num_batches == (id + 1), "can only remove active or last batch"); + camp_tbl.modify(camp, same_payer, + [&](auto& c) { c.num_batches -= 1; c.total_tasks -= batch->num_tasks; }); + batch_tbl.erase(batch); + } else if (batch->id == camp->active_batch) { + // if its the active batch, move onward + // batch_tbl.modify(batch, eosio::same_payer, [&](auto& b) { b.num_tasks = batch_tasks_done; }); + uint32_t batch_tasks_remaining = batch->num_tasks - batch_tasks_done; + camp_tbl.modify(camp, eosio::same_payer, + [&](auto& c) + { + c.active_batch += 1; + c.total_tasks -= batch_tasks_remaining; + }); + } else { + // if the batch is in the past, we can erase it + batch_tbl.erase(batch); + } + + // TODO: refund remaining balance } void force::cleartasks(uint32_t batch_id, uint32_t campaign_id) { @@ -117,7 +156,7 @@ void force::cleartasks(uint32_t batch_id, uint32_t campaign_id) { auto batch_itr = batch_tbl.find(batch_pk); eosio::check(batch_itr == batch_tbl.end(), "batch still exists"); - // remove the submissoins in this batch + // remove the submissions in this batch submission_table submission_tbl(_self, _self.value); auto by_batch = submission_tbl.get_index<"batch"_n>(); auto itr_start = by_batch.lower_bound(batch_pk); @@ -133,7 +172,7 @@ void force::cleartasks(uint32_t batch_id, uint32_t campaign_id) { } } -void force::publishbatch(uint64_t batch_id, uint32_t num_tasks, vaccount::sig sig) { +void force::publishbatch(uint64_t batch_id, uint32_t num_tasks) { batch_table batch_tbl(_self, _self.value); auto& batch = batch_tbl.get(batch_id, "batch not found"); @@ -143,10 +182,9 @@ void force::publishbatch(uint64_t batch_id, uint32_t num_tasks, vaccount::sig si settings settings = get_settings(); - reopenbatch_params params = {17, batch_id}; - vaccount::require_auth(pack(params), camp.owner, sig); + vaccount::require_auth(std::vector(), camp.owner, std::nullopt); - eosio::extended_asset task_reward = batch.reward.value(); + eosio::extended_asset task_reward = batch.reward; eosio::extended_asset batch_fee(task_reward.quantity.amount * settings.fee_percentage * num_tasks * batch.repetitions, task_reward.get_extended_symbol()); @@ -159,221 +197,273 @@ void force::publishbatch(uint64_t batch_id, uint32_t num_tasks, vaccount::sig si [&](auto& b) { b.num_tasks = num_tasks; b.balance -= batch_fee; + // if this batch becomes the active batch of the + // campaign track it starting index + if (camp.active_batch == b.id) { + b.start_task_idx = camp.reservations_done; + } }); + camp_tbl.modify(camp, + eosio::same_payer, + [&](auto& b) { + b.total_tasks += num_tasks; + }); + if (batch_fee.quantity.amount > 0) { action(permission_level{_self, "xfer"_n}, - settings.vaccount_contract, - "withdraw"_n, - std::make_tuple((uint64_t) settings.force_vaccount_id, + batch.balance.contract, + "transfer"_n, + std::make_tuple(_self, settings.fee_contract, - batch_fee, - std::string("batch " + std::to_string(batch_id)), - NULL, - NULL)) + batch_fee.quantity, + std::string("batch " + std::to_string(batch_id)))) .send(); } } -void force::mkquali(content content, uint32_t account_id, eosio::name payer, vaccount::sig sig) { - mkquali_params params = {18, account_id, content}; - require_vaccount(account_id, pack(params), sig); - - quali_table quali_tbl(_self, _self.value); - uint32_t quali_id = quali_tbl.available_primary_key(); - quali_tbl.emplace(payer, - [&](auto& q) - { - q.id = quali_id; - q.content = content; - q.account_id = account_id; - }); -} - - -void force::editquali(uint32_t quali_id, content content, uint32_t account_id, - eosio::name payer, vaccount::sig sig) { - quali_table quali_table(_self, _self.value); - auto& quali = quali_table.get(quali_id, "qualification does not exist"); - - editquali_params params = {20, quali_id, content}; - std::vector msg_bytes = pack(params); - require_vaccount(account_id, pack(params), sig); +void force::reservetask(uint32_t campaign_id, + uint32_t account_id, + std::optional> quali_assets) { + campaign_table campaign_tbl(_self, _self.value); + auto& campaign = campaign_tbl.get(campaign_id, "campaign not found"); - quali_table.modify(quali, payer, [&] (auto& q) { q.content = content; }); -} + eosio::check(!campaign.paused, "campaign is paused"); -void force::assignquali(uint32_t quali_id, uint32_t user_id, std::string value, - eosio::name payer, vaccount::sig sig) { - quali_table quali_tbl(_self, _self.value); - auto quali = quali_tbl.get(quali_id, "qualification not found"); - assignquali_params params = {19, quali_id, user_id, value}; - require_vaccount(quali.account_id, pack(params), sig); + // check qualifications + settings settings = get_settings(); + auto vacc = vaccount::get_vaccount(settings.vaccount_contract, account_id); + bool is_eos = vaccount::is_eos(vacc->address); + eosio::name asset_owner = is_eos ? vaccount::get_name(vacc->address) : _self; + eosio::name payer = asset_owner; + auto acc_assets_tbl = atomicassets::get_assets(asset_owner); + auto force_assets_tbl = atomicassets::get_assets(_self); + + if (campaign.qualis.size() > 0) + eosio::check(quali_assets.has_value() && quali_assets.value().size() == campaign.qualis.size(), + "wrong number of quali_assets"); + + for (int i = 0; i < campaign.qualis.size(); i++) { + auto quali = campaign.qualis[i]; + uint64_t asset_id = quali_assets.value()[i]; + atomicassets::assets_s asset; + + // check right asset owner + auto acc_asset = acc_assets_tbl.find(asset_id); + auto force_asset = force_assets_tbl.find(asset_id); + bool asset_is_eos = (acc_asset != acc_assets_tbl.end()); + eosio::check(asset_is_eos || force_asset != force_assets_tbl.end(), + "asset now owned by you"); + asset = asset_is_eos ? *acc_asset : *force_asset; + + // if this is a vaccount, we must additionaly check the asset is owned by it + if (!asset_is_eos) { + auto schema_tbl = atomicassets::get_schemas(asset.collection_name); + auto schema = schema_tbl.get(asset.schema_name.value, "asset not owned by vaccount"); + auto data_map = atomicdata::deserialize(asset.mutable_serialized_data, schema.format); + auto asset_vacc_owner = std::get(data_map["vaccount"]); + eosio::check(asset_vacc_owner == account_id, "asset not owned by vaccount"); + } - user_quali_table user_quali_tbl(_self, _self.value); - user_quali_tbl.emplace(payer, - [&](auto& q) - { - q.account_id = user_id; - q.quali_id = quali_id; - q.value.emplace(value); - }); -} + switch (quali.type) { + case Collection: + eosio::check(asset.collection_name == std::get(quali.address), + "wrong collection"); + break; + case Template: + eosio::check(asset.template_id == std::get(quali.address), + "wrong template"); + break; + case Asset: + eosio::check(asset.asset_id == std::get(quali.address), + "wrong asset"); + break; + } + } -void force::uassignquali(uint32_t quali_id, uint32_t user_id, eosio::name payer, vaccount::sig sig) { - quali_table quali_tbl(_self, _self.value); - auto quali = quali_tbl.get(quali_id, "qualification not found"); - rmbatch_params params = {20, quali_id, user_id}; - require_vaccount(quali.account_id, pack(params), sig); - - uint64_t user_quali_key = (uint64_t{user_id} << 32) | quali_id; - user_quali_table user_quali_tbl(_self, _self.value); - auto user_quali = user_quali_tbl.find(user_quali_key); - eosio::check(user_quali != user_quali_tbl.end(), "user does not have quali"); - user_quali_tbl.erase(user_quali); -} + // check if user has a reservation already + uint64_t acccamp_pk = (uint64_t{account_id} << 32) | campaign_id; + reservation_table reservation_tbl(_self, _self.value); + auto by_acccamp = reservation_tbl.get_index<"acccamp"_n>(); + auto existing_reservation = by_acccamp.find(acccamp_pk); + eosio::check(existing_reservation == by_acccamp.end(), "you already have a reservation"); + + // find the last task idx the user completed in the campaign + acctaskidx_table acctaskidx_tbl(_self, _self.value); + auto our_task_idx = acctaskidx_tbl.find(acccamp_pk); + auto user_has_last_task = (our_task_idx != acctaskidx_tbl.end()); + + // if this is the first task done by this user, insert the task index + if (!user_has_last_task) { + acctaskidx_tbl.emplace(payer, + [&](auto& i) + { + i.campaign_id = campaign_id; + i.account_id = account_id; + i.batch_idx = campaign.active_batch; + i.value = 0; + }); + } -void force::require_batchjoin(uint32_t account_id, uint64_t batch_pk, bool try_to_join, name payer) { - batchjoin_table join_tbl(_self, _self.value); - uint128_t pk = (uint128_t{batch_pk} << 64) | (uint64_t{account_id} << 32); + auto& user_last_task = acctaskidx_tbl.get(acccamp_pk); + uint32_t user_next_task_idx = !user_has_last_task ? 0 : user_last_task.value + 1; - auto by_accbatch = join_tbl.get_index<"accbatch"_n>(); - auto entry = by_accbatch.find(pk); - bool has_joined = (entry != by_accbatch.end()); - if (!has_joined) { - eosio::check(try_to_join, "batch not joined"); + require_vaccount(account_id); - batch_table batch_tbl(_self, _self.value); - auto batch = batch_tbl.get(batch_pk, "batch not found"); - campaign_table camp_tbl(_self, _self.value); - auto camp = camp_tbl.get(batch.campaign_id, "campaign not found"); - user_quali_table user_quali_tbl(_self, _self.value); + eosio::check(!user_has_last_task || campaign.total_tasks > user_last_task.value, + "no more tasks for you"); - std::map qualis; + // reserve suitable task idx to the user + uint32_t task_idx = std::max(campaign.reservations_done, user_next_task_idx); - if (!camp.qualis.has_value() && batch.qualis.has_value()) { - qualis = batch.qualis.value(); - } else { - qualis = camp.qualis.value(); - if (batch.qualis.has_value()) { - qualis.insert(batch.qualis.value().begin(), batch.qualis.value().end()); - } - } + submission_table submission_tbl(_self, _self.value); - for (auto q : qualis) { - uint32_t quali_id = std::get<0>(q); - uint8_t quali_type = std::get<1>(q); - uint64_t user_quali_id = (uint64_t{account_id} << 32) | quali_id; - auto user_quali = user_quali_tbl.find(user_quali_id); - bool has_quali = (user_quali != user_quali_tbl.end()); - if (has_quali) - eosio::check(quali_type == force::Inclusive, "qualification excluded"); - else - eosio::check(quali_type == force::Exclusive, "missing qualification"); - } + // fetch active batch information + uint32_t batch_id = (user_has_last_task && our_task_idx->batch_idx > campaign.active_batch) ? + our_task_idx->batch_idx : campaign.active_batch; - uint64_t join_id = join_tbl.available_primary_key(); - join_tbl.emplace(payer, - [&](auto& j) + uint64_t batch_pk = (uint64_t{campaign_id} << 32) | batch_id; + batch_table batch_tbl(_self, _self.value); + auto& batch = batch_tbl.get(batch_pk, "no batches available"); + + eosio::check(campaign.reservations_done < batch.start_task_idx + batch.num_tasks, + "no more tasks in campaign"); + + // check if this task index lays in the next batch + if (task_idx >= (batch.start_task_idx + batch.num_tasks)) { + uint64_t next_batch_pk = (uint64_t{campaign_id} << 32) | (batch_id + 1); + auto next_batch = batch_tbl.find(next_batch_pk); + batch_pk = next_batch_pk; + batch_id = batch_id + 1; + eosio::check(next_batch != batch_tbl.end(), "next batch not available"); + + batch_tbl.modify(*next_batch, + eosio::same_payer, + [&](auto &b) { - j.account_id = account_id; - j.batch_id = batch_pk; - j.id = join_id; + b.start_task_idx = task_idx; }); + acctaskidx_tbl.modify(*our_task_idx, + eosio::same_payer, + [&](auto &i) + { + i.batch_idx = batch_id; + }); } -} - -void force::require_merkle(std::vector proof, std::vector position, - checksum256 root, checksum256 data_hash) { - uint8_t hashlen = 32; - checksum256 last_hash = data_hash; - - for (size_t i = 0; i < proof.size(); i++) { - std::array arr1 = last_hash.extract_as_byte_array(); - std::array arr2 = proof[i].extract_as_byte_array(); - - std::array combined; - if (position[i] == 1) { - std::copy(arr1.cbegin(), arr1.cend(), combined.begin()); - std::copy(arr2.cbegin(), arr2.cend(), combined.begin() + 32); - } else if (position[i] == 0) { - std::copy(arr2.cbegin(), arr2.cend(), combined.begin()); - std::copy(arr1.cbegin(), arr1.cend(), combined.begin() + 32); - } - void* ptr = static_cast(combined.data()); - last_hash = sha256((char*) ptr, 64); + // check if there is an earlier expired reservation to claim instead + auto by_camp = reservation_tbl.get_index<"camp"_n>(); + auto by_camp_itr = by_camp.find(campaign_id); + if (by_camp_itr != by_camp.end() && + has_expired(by_camp_itr->reserved_on, campaign.max_task_time) && + // only claim reservations that come before our assigned task + // idx. if the user were to steal future indexis, bumping + // acctaskidx would mean the users misses out on tasks, and + // omitting so would let him do this repetition twice. + // + // NOTE: this does allow users to complete reptitions twice, + // when they expire? + task_idx >= by_camp_itr->task_idx) { + auto& res = *by_camp_itr; + uint64_t bump_id = std::max(reservation_tbl.available_primary_key(), + submission_tbl.available_primary_key()); + + // we must re-insert the reservation in order to bump the id + reservation_tbl.erase(res); + reservation_tbl.emplace(payer, + [&](auto& r) + { + r.id = bump_id; + r.task_idx = res.task_idx; + r.account_id = account_id; + r.batch_idx = batch_id; + r.reserved_on = time_point_sec(now()); + r.campaign_id = campaign_id; + }); + // NOTE: early return here + return; } - eosio::check(root == last_hash, "invalid merkle proof"); -} - -void force::reservetask(std::vector proof, std::vector position, - std::vector data, uint32_t campaign_id, uint32_t batch_id, - uint32_t account_id, name payer, vaccount::sig sig) { - submission_table submission_tbl(_self, _self.value); - batch_table batch_tbl(_self, _self.value); - campaign_table campaign_tbl(_self, _self.value); - - uint64_t batch_pk = (uint64_t{campaign_id} << 32) | batch_id; - require_batchjoin(account_id, batch_pk, true, payer); - - auto& batch = batch_tbl.get(batch_pk, "batch not found"); - auto& campaign = campaign_tbl.get(campaign_id, "campaign not found"); - - eosio::check(batch.tasks_done >= 0 && batch.num_tasks > 0, "batch paused"); - // TODO: verify depth of tree so cant be spoofed with partial proof - - // we prepend the batch_pk to the data so that each data point has a unique - // entry in the submission table. - const uint16_t datasize = data.size() + sizeof batch_pk; - char buff[datasize]; - std::copy(static_cast(static_cast(&batch_pk)), - static_cast(static_cast(&batch_pk)) + sizeof batch_pk, - &buff[0]); - std::copy(data.cbegin(), data.cend(), &buff[0] + sizeof batch_pk); - - checksum256 data_hash = sha256(&buff[0], datasize); - reservetask_params params = {6, data_hash, campaign_id, batch_id}; - require_vaccount(account_id, pack(params), sig); - - // printhex(&data_hash.extract_as_byte_array()[0], 32); - require_merkle(proof, position, batch.task_merkle_root, data_hash); + // check how many reps are done for this task + uint64_t repsdone_pk = (uint64_t{campaign_id} << 32) | task_idx; + repsdone_table repsdone_tbl(_self, _self.value); + auto repetitions_done = repsdone_tbl.find(repsdone_pk); - uint32_t submission_id = submission_tbl.available_primary_key(); + // check if this task is done + bool has_reps_done_row = !(repetitions_done == repsdone_tbl.end()); + bool task_done = false; + if (batch.repetitions == 1) { + task_done = true; + } else { + if (!has_reps_done_row) { + repsdone_tbl.emplace(payer, + [&](auto& i) + { + i.campaign_id = campaign_id; + i.task_idx = task_idx; + i.value = 1; + }); + } else { + if (repetitions_done->value + 1 == batch.repetitions) + task_done = true; + } + } - // check if repetitions are not done - auto by_leaf = submission_tbl.get_index<"leaf"_n>(); - auto itr_start = by_leaf.lower_bound(data_hash); - auto itr_end = by_leaf.upper_bound(data_hash); - uint32_t rep_count = 0; + // update campaign counters if task is done + acctaskidx_tbl.modify(user_last_task, payer, [&](auto& i) { i.value = task_idx; }); + if (task_done) { + if (has_reps_done_row) + repsdone_tbl.erase(repetitions_done); + + bool batch_done = ((campaign.reservations_done + 1) >= + (batch.start_task_idx + batch.num_tasks)); + campaign_tbl.modify(campaign, + eosio::same_payer, + [&](auto& c) + { + c.reservations_done += 1; + if (batch_done) { + c.active_batch += 1; + } + }); - for (; itr_start != itr_end; itr_start++) { - rep_count++; - auto& subm = *itr_start; - eosio::check(subm.account_id != account_id, "account already did task"); - } - eosio::check(rep_count < batch.repetitions, "task already completed"); + // if we roll over to a new batch, we must track at which task index it starts + if (batch_done) { + uint64_t next_batch_pk = (uint64_t{campaign_id} << 32) | (batch_id + 1); + auto next_batch = batch_tbl.find(next_batch_pk); - submission_tbl.emplace(payer, - [&](auto& s) + if (next_batch != batch_tbl.end()) { + batch_tbl.modify(*next_batch, + eosio::same_payer, + [&](auto &b) { - s.id = submission_id; - s.account_id = account_id; - s.leaf_hash = data_hash; - s.batch_id = batch_pk; - s.paid = false; - s.submitted_on = time_point_sec(now()); + b.start_task_idx = campaign.reservations_done; }); + } + } + } + + uint64_t reservation_id = std::max(reservation_tbl.available_primary_key(), + submission_tbl.available_primary_key()); + + reservation_tbl.emplace(payer, + [&](auto& r) + { + r.id = reservation_id; + r.task_idx = task_idx; + r.account_id.emplace(account_id); + r.batch_idx = batch_id; + r.reserved_on = time_point_sec(now()); + r.campaign_id = campaign_id; + }); } -void force::payout(uint64_t payment_id, std::optional sig) { +void force::payout(uint64_t payment_id) { payment_table payment_tbl(_self, _self.value); auto& payment = payment_tbl.get(payment_id, "payment not found"); - payout_params params = {13, payment.account_id}; - require_vaccount(payment.account_id, pack(params), sig); + require_vaccount(payment.account_id); eosio::check(past_delay(payment.last_submission_time, "payout"), "not past payout delay"); eosio::check(payment.pending.quantity.amount > 0, "nothing to payout"); @@ -393,31 +483,58 @@ void force::payout(uint64_t payment_id, std::optional sig) { .send(); } -void force::submittask(uint64_t submission_id, std::string data, uint32_t account_id, - uint64_t batch_id, name payer, vaccount::sig sig) { +void force::submittask(uint32_t campaign_id, + uint32_t task_idx, + std::pair> data, + uint32_t account_id) { + uint64_t acccamp_pk = (uint64_t{account_id} << 32) | campaign_id; + reservation_table reservation_tbl(_self, _self.value); + auto by_acccamp = reservation_tbl.get_index<"acccamp"_n>(); + auto res = by_acccamp.find(acccamp_pk); + + eosio::check(res != by_acccamp.end(), "already submitted or not reserved by you"); + eosio::check(res->account_id.has_value(), "not reserved"); + eosio::check(res->account_id.value() == account_id, "different account"); + eosio::check(res->task_idx == task_idx, "wrong task index"); + submission_table submission_tbl(_self, _self.value); payment_table payment_tbl(_self, _self.value); batch_table batch_tbl(_self, _self.value); campaign_table campaign_tbl(_self, _self.value); - auto& sub = submission_tbl.get(submission_id, "submission not found"); - eosio::check(sub.account_id.has_value(), "task not reserved"); - eosio::check(sub.account_id == account_id, "different account"); - eosio::check(!sub.data.has_value(), "already submitted"); - submission_tbl.modify(sub, payer, [&](auto& s) { s.data.emplace(data); }); + settings settings = get_settings(); + auto vacc = vaccount::get_vaccount(settings.vaccount_contract, account_id); + eosio::check(vaccount::is_eos(vacc->address), "wrong vaccount type"); + eosio::name payer = vaccount::get_name(vacc->address); + + reservation_tbl.erase(*res); + submission_tbl.emplace(payer, + [&](auto& s) + { + s.id = res->id; + s.campaign_id = campaign_id; + s.task_idx = task_idx; + s.account_id.emplace(account_id); + s.data = data; + s.batch_idx = res->batch_idx; + s.paid = false; + s.submitted_on = time_point_sec(now()); + }); - auto& batch = batch_tbl.get(sub.batch_id, "batch not found"); - auto& camp = campaign_tbl.get(batch.campaign_id); + auto& campaign = campaign_tbl.get(campaign_id, "campaign not found"); + campaign_tbl.modify(campaign, + eosio::same_payer, + [&](auto& c) { c.total_submissions += 1; }); - batch_tbl.modify(batch, eosio::same_payer, [&](auto& b) { b.tasks_done++; }); + uint64_t batch_pk = (uint64_t{campaign_id} << 32) | res->batch_idx; + auto& batch = batch_tbl.get(batch_pk); - if (batch.reward.value().quantity.amount > 0) { - submittask_params params = {5, submission_id, data}; - require_vaccount(account_id, pack(params), sig); + require_vaccount(account_id); + if (batch.reward.quantity.amount > 0) { uint64_t payment_id = payment_tbl.available_primary_key(); - uint128_t payment_sk = (uint128_t{batch_id} << 64) | (uint64_t{account_id} << 32); + uint128_t payment_sk = (uint128_t{batch_pk} << 64) | (uint64_t{account_id} << 32); auto payment_idx = payment_tbl.get_index<"accbatch"_n>(); auto payment = payment_idx.find(payment_sk); @@ -427,8 +544,8 @@ void force::submittask(uint64_t submission_id, std::string data, uint32_t accoun { p.id = payment_id; p.account_id = account_id; - p.batch_id = batch_id; - p.pending = batch.reward.value(); + p.batch_id = res->batch_idx; + p.pending = batch.reward; p.last_submission_time = time_point_sec(now()); }); } else { @@ -436,96 +553,22 @@ void force::submittask(uint64_t submission_id, std::string data, uint32_t accoun payer, [&](auto& p) { - p.pending += batch.reward.value(); + p.pending += batch.reward; p.last_submission_time = time_point_sec(now()); }); } } } -void force::releasetask(uint64_t task_id, uint32_t account_id, - eosio::name payer, vaccount::sig sig) { - submission_table submission_tbl(_self, _self.value); - batch_table batch_tbl(_self, _self.value); - campaign_table campaign_tbl(_self, _self.value); - auto vacc_contract = get_settings().vaccount_contract; - - vaccount::account_table acc_tbl(vacc_contract, vacc_contract.value); - vaccount::account acc = acc_tbl.get((uint64_t) account_id, "account row not found"); - - auto& sub = submission_tbl.get(task_id, "reservation not found"); - - auto& batch = batch_tbl.get(sub.batch_id, "batch not found"); - auto& camp = campaign_tbl.get(batch.campaign_id, "campaign not found"); - - eosio::check(sub.account_id.has_value(), "cannot release already released task."); - eosio::check(!sub.data.has_value(), "cannot release task with data."); - - task_params params = {14, task_id, account_id}; - std::vector msg_bytes = pack(params); - - if (sub.account_id == account_id) { - require_vaccount(account_id, msg_bytes, sig); - } - else if (acc.address == camp.owner) { - vaccount::require_auth(msg_bytes, acc.address, sig); - } else { - eosio::check(past_delay(sub.submitted_on, "release_task"), "cannot release task before delay."); +void force::transfer_handler(eosio::name from, + eosio::name to, + eosio::asset quantity, + std::string memo) { + if (to == get_self()) { + eosio::extended_asset sym(quantity, get_first_receiver()); + uint64_t batch_id = std::stoull(memo); + batch_table batch_tbl(_self, _self.value); + auto& batch = batch_tbl.get(batch_id, "batch not found"); + batch_tbl.modify(batch, eosio::same_payer, [&](auto& b) { b.balance += sym; }); } - - submission_tbl.modify(sub, eosio::same_payer, [&](auto& s) { s.account_id.reset(); }); -} - -void force::reclaimtask(uint64_t task_id, uint32_t account_id, - eosio::name payer, vaccount::sig sig) { - submission_table submission_tbl(_self, _self.value); - batch_table batch_tbl(_self, _self.value); - - auto& sub = submission_tbl.get(task_id, "reservation not found"); - auto& batch = batch_tbl.get(sub.batch_id, "batch not found"); - - eosio::check(batch.tasks_done >= 0 && batch.num_tasks > 0, - "cannot reclaim task on paused batch."); - - require_batchjoin(account_id, sub.batch_id, true, payer); - - task_params params = {15, task_id, account_id}; - require_vaccount(account_id, pack(params), sig); - - eosio::check(!sub.account_id.has_value(), "task already reserved"); - eosio::check(!sub.data.has_value(), "task already submitted"); - submission_tbl.modify(sub, - payer, - [&](auto& s) - { - s.account_id = account_id; - s.submitted_on = time_point_sec(now()); - }); -} - -void force::closebatch(uint64_t batch_id, vaccount::vaddress owner, vaccount::sig sig) { - batch_table batch_tbl(_self, _self.value); - campaign_table camp_tbl(_self, _self.value); - - auto& batch = batch_tbl.get(batch_id, "batch not found"); - auto& camp = camp_tbl.get(batch.campaign_id, "campaign not found"); - - closebatch_params params = {16, batch_id}; - - eosio::check(camp.owner == owner, "Only campaign owner can pause batch."); - vaccount::require_auth(pack(params), owner, sig); - eosio::check(batch.tasks_done >= 0 && batch.num_tasks > 0 && - batch.tasks_done < batch.num_tasks * batch.repetitions, - "can only pause batches with active tasks."); - - batch_tbl.modify(batch, eosio::same_payer, [&](auto& b) { b.num_tasks = 0; }); -} - -void force::vtransfer_handler(uint64_t from_id, uint64_t to_id, extended_asset quantity, - std::string memo, vaccount::sig sig, - std::optional fee) { - uint64_t batch_id = std::stoull(memo); - batch_table batch_tbl(_self, _self.value); - auto& batch = batch_tbl.get(batch_id, "batch not found"); - batch_tbl.modify(batch, eosio::same_payer, [&](auto& b) { b.balance += quantity; }); } diff --git a/contracts/force/force.hpp b/contracts/force/force.hpp index bebd59c..7fe1ab9 100644 --- a/contracts/force/force.hpp +++ b/contracts/force/force.hpp @@ -8,6 +8,8 @@ #include #include #include "../vaccount/vaccount-shared.hpp" +#include "../dao/atomicassets-interface.hpp" +#include "atomicdata.hpp" using namespace eosio; @@ -27,339 +29,195 @@ class [[eosio::contract("force")]] force : public eosio::contract { typedef std::tuple content; - enum QualiType { - Inclusive = 0, - Exclusive = 1 + // assets is uint64_t, templates are uint32_t, collections and schemas are eosio::name + typedef std::variant QUALI_ATOMIC_ADDRESS; + + // this allocates some data for in the future + struct QualiDataFilter { + uint8_t attr_id; + uint8_t filter_type; + std::vector data; }; - template - void cleanTable(name code, uint64_t account, const uint32_t batchSize){ - T db(code, account); - uint32_t counter = 0; - auto itr = db.begin(); - while (itr != db.end() && counter++ < batchSize) { - itr = db.erase(itr); - } - } + struct Quali { + uint8_t type; + QUALI_ATOMIC_ADDRESS address; + std::optional data_filter; + }; + + enum QualiType { + Collection = 0, + Template = 1, + Asset = 2 + }; force(eosio::name receiver, eosio::name code, eosio::datastream ds) : - eosio::contract(receiver, code, ds), _config(_self, _self.value), _settings(_self, _self.value) + eosio::contract(receiver, code, ds), _settings(_self, _self.value) {}; [[eosio::action]] void init(eosio::name vaccount_contract, uint32_t force_vaccount_id, uint32_t payout_delay_sec, - uint32_t release_task_delay_sec); + uint32_t release_task_delay_sec, + eosio::name fee_contract, + float fee_percentage); [[eosio::action]] void mkcampaign(vaccount::vaddress owner, content content, + uint32_t max_task_time, eosio::extended_asset reward, - camp_quali_map qualis, - eosio::name payer, - vaccount::sig sig); + std::vector qualis, + eosio::name payer); [[eosio::action]] void editcampaign(uint32_t campaign_id, vaccount::vaddress owner, content content, + bool paused, eosio::extended_asset reward, - camp_quali_map qualis, - eosio::name payer, - vaccount::sig sig); + std::vector qualis, + eosio::name payer); [[eosio::action]] void rmcampaign(uint32_t campaign_id, - vaccount::vaddress owner, - vaccount::sig sig); + vaccount::vaddress owner); [[eosio::action]] void mkbatch(uint32_t id, uint32_t campaign_id, content content, - checksum256 task_merkle_root, uint32_t repetitions, - std::optional qualis, - eosio::name payer, - vaccount::sig sig); + eosio::name payer); [[eosio::action]] void publishbatch(uint64_t batch_id, - uint32_t num_tasks, - vaccount::sig sig); + uint32_t num_tasks); [[eosio::action]] void rmbatch(uint32_t id, - uint32_t campaign_id, - vaccount::sig sig); + uint32_t campaign_id); [[eosio::action]] void cleartasks(uint32_t batch_id, uint32_t campaign_id); [[eosio::action]] - void closebatch(uint64_t batch_id, - vaccount::vaddress owner, - vaccount::sig sig); - - [[eosio::action]] - void reservetask(std::vector proof, - std::vector position, - std::vector data, - uint32_t campaign_id, - uint32_t batch_id, + void reservetask(uint32_t campaign_id, uint32_t account_id, - eosio::name payer, - vaccount::sig sig); + std::optional> quali_assets); [[eosio::action]] - void submittask(uint64_t task_id, - std::string data, - uint32_t account_id, - uint64_t batch_id, - eosio::name payer, - vaccount::sig sig); + void submittask(uint32_t campaign_id, + uint32_t task_idx, + std::pair> data, + uint32_t account_id); [[eosio::action]] - void payout(uint64_t payment_id, - std::optional sig); + void payout(uint64_t payment_id); - [[eosio::action]] - void releasetask(uint64_t task_id, - uint32_t account_id, - eosio::name payer, - vaccount::sig sig); + [[eosio::on_notify("*::transfer")]] + void transfer_handler(eosio::name from_id, + eosio::name to_id, + eosio::asset quantity, + std::string memo); - [[eosio::action]] - void reclaimtask(uint64_t task_id, - uint32_t account_id, - eosio::name payer, - vaccount::sig sig); - - [[eosio::action]] - void mkquali(content content, - uint32_t account_id, - eosio::name payer, - vaccount::sig sig); - - [[eosio::action]] - void editquali(uint32_t quali_id, - content content, - uint32_t account_id, - eosio::name payer, - vaccount::sig sig); - - - [[eosio::action]] - void assignquali(uint32_t quali_id, - uint32_t user_id, - std::string value, - eosio::name payer, - vaccount::sig sig); - - [[eosio::action]] - void uassignquali(uint32_t quali_id, - uint32_t user_id, - eosio::name payer, - vaccount::sig sig); - - [[eosio::on_notify("*::vtransfer")]] - void vtransfer_handler(uint64_t from_id, - uint64_t to_id, - extended_asset quantity, - std::string memo, - vaccount::sig sig, - std::optional fee); + template + void cleanTable(name code, uint64_t account, const uint32_t batchSize){ + T db(code, account); + uint32_t counter = 0; + auto itr = db.begin(); + while (itr != db.end() && counter++ < batchSize) { + itr = db.erase(itr); + } + } [[eosio::action]] void clean() { require_auth(_self); + cleanTable(_self, _self.value, 100); + cleanTable(_self, _self.value, 100); cleanTable(_self, _self.value, 100); + cleanTable(_self, _self.value, 100); cleanTable(_self, _self.value, 100); cleanTable(_self, _self.value, 100); cleanTable(_self, _self.value, 100); - cleanTable(_self, _self.value, 100); }; - [[eosio::action]] - void migrate(eosio::name payer, eosio::name fee_contract, float fee_percentage) { - require_auth(_self); - auto c = _config.get(); - auto itr = _settings.find(settings_pk.value); - eosio::check(itr == _settings.end(), "already migrated"); - - _settings.emplace(payer, - [&](auto& s) - { - s.vaccount_contract = c.vaccount_contract; - s.force_vaccount_id = c.force_vaccount_id; - s.payout_delay_sec = c.payout_delay_sec; - s.release_task_delay_sec = c.release_task_delay_sec; - s.fee_contract = fee_contract; - s.fee_percentage = fee_percentage; - }); +private: + inline bool has_expired(time_point_sec base_time, uint32_t delay) { + return time_point_sec(now()) >= (base_time + delay); } -private: inline bool past_delay(time_point_sec base_time, std::string type_delay) { auto delay = NULL; - if (type_delay == "payout") delay = _config.get().payout_delay_sec; - else if (type_delay == "release_task") delay = _config.get().release_task_delay_sec; - return time_point_sec(now()) > (base_time + delay); + if (type_delay == "payout") delay = get_settings().payout_delay_sec; + else if (type_delay == "release_task") delay = get_settings().release_task_delay_sec; + return time_point_sec(now()) >= (base_time + delay); } - // Helper. Assumes we already did require_vaccount on account_id - void require_batchjoin(uint32_t account_id, - uint64_t batch_id, - bool try_to_join, - eosio::name payer); - - void require_merkle(std::vector proof, - std::vector position, - eosio::checksum256 root, - eosio::checksum256 data); - - struct reservetask_params { - uint8_t mark; - checksum256 leaf_hash; - uint32_t campaign_id; - uint32_t batch_id; - EOSLIB_SERIALIZE(reservetask_params, (mark)(leaf_hash)(campaign_id)(batch_id)); - }; - - struct submittask_params { - uint8_t mark; - uint64_t submission_id; - std::string data; - EOSLIB_SERIALIZE(submittask_params, (mark)(submission_id)(data)); - }; - - struct mkcampaign_params { - uint8_t mark; - content content; - EOSLIB_SERIALIZE(mkcampaign_params, (mark)(content)); - }; - - struct editcampaign_params { - uint8_t mark; - uint32_t campaign_id; - content content; - EOSLIB_SERIALIZE(editcampaign_params, (mark)(campaign_id)(content)); - }; - - struct rmcampaign_params { - uint8_t mark; - uint32_t campaign_id; - EOSLIB_SERIALIZE(rmcampaign_params, (mark)(campaign_id)); - }; - - struct mkquali_params { - uint8_t mark; - uint32_t account_id; - content content; - EOSLIB_SERIALIZE(mkquali_params, (mark)(account_id)(content)); - }; - - struct editquali_params { - uint8_t mark; - uint32_t quali_id; - content content; - EOSLIB_SERIALIZE(editquali_params, (mark)(quali_id)(content)); - }; - - struct mkbatch_params { - uint8_t mark; - uint32_t id; - uint32_t campaign_id; - content content; - checksum256 task_merkle_root; - EOSLIB_SERIALIZE(mkbatch_params, (mark)(id)(campaign_id)(content)(task_merkle_root)); - }; - - struct closebatch_params { - uint8_t mark; - uint64_t id; - EOSLIB_SERIALIZE(closebatch_params, (mark)(id)); - }; - - struct reopenbatch_params { - uint8_t mark; - uint64_t id; - EOSLIB_SERIALIZE(reopenbatch_params, (mark)(id)); - }; - - struct rmbatch_params { - uint8_t mark; - uint32_t id; - uint32_t campaign_id; - EOSLIB_SERIALIZE(rmbatch_params, (mark)(id)(campaign_id)); - }; - - struct assignquali_params { - uint8_t mark; - uint32_t id; - uint32_t campaign_id; - std::string value; - EOSLIB_SERIALIZE(assignquali_params, (mark)(id)(campaign_id)(value)); - }; - - struct joinbatch_params { - uint8_t mark; - uint64_t batch_id; - EOSLIB_SERIALIZE(joinbatch_params, (mark)(batch_id)); - }; - - struct payout_params { - uint8_t mark; - uint32_t payment_id; - EOSLIB_SERIALIZE(payout_params, (mark)(payment_id)); - }; - - struct task_params { - uint8_t mark; - uint64_t task_id; - uint32_t account_id; - EOSLIB_SERIALIZE(task_params, (mark)(task_id)(account_id)); - }; - struct [[eosio::table]] campaign { uint32_t id; + /** + * Counter for the reservations that have been created. This + * counter only goes up if all repetitions for a task have been + * reserved. + */ + uint32_t reservations_done; + /** + * Counter for the total submissions that exist for this campaign. + * This counter goes up for every repetition that is submitted. + */ + uint32_t total_submissions; + /** + * Total tasks in this campaign. + */ + uint32_t total_tasks; + uint32_t active_batch; + uint32_t num_batches; vaccount::vaddress owner; + bool paused; content content; + uint32_t max_task_time; eosio::extended_asset reward; - eosio::binary_extension> qualis; + std::vector qualis; uint64_t primary_key() const { return (uint64_t) id; } - EOSLIB_SERIALIZE(campaign, (id)(owner)(content)(reward)(qualis)) + EOSLIB_SERIALIZE(campaign, (id)(reservations_done)(total_submissions)(total_tasks) + (active_batch)(num_batches)(owner)(paused)(content)(max_task_time)(reward) + (qualis)) }; struct [[eosio::table]] batch { uint32_t id; uint32_t campaign_id; content content; - checksum256 task_merkle_root; eosio::extended_asset balance; uint32_t repetitions; - uint32_t tasks_done; uint32_t num_tasks; - eosio::binary_extension> qualis; - eosio::binary_extension reward; + uint32_t start_task_idx; + eosio::extended_asset reward; uint64_t primary_key() const { return (uint64_t{campaign_id} << 32) | id; } - uint32_t by_campaign() const { return campaign_id; } }; - struct [[eosio::table]] batchjoin { - // This id is only necessary for uniqueness of primary key - uint64_t id; + struct [[eosio::table]] acctaskidx { uint32_t account_id; - uint64_t batch_id; - uint64_t primary_key() const { return id; } - uint128_t by_account_batch() const { return (uint128_t{batch_id} << 64) | (uint64_t{account_id} << 32); } + uint32_t campaign_id; + uint32_t batch_idx; + uint32_t value; + uint64_t primary_key() const { return (uint64_t{account_id} << 32) | campaign_id; } + }; + + struct [[eosio::table]] repsdone { + uint32_t campaign_id; + uint32_t task_idx; + uint32_t value; + uint64_t primary_key() const { return (uint64_t{campaign_id} << 32) | task_idx; } }; struct [[eosio::table]] payment { @@ -376,69 +234,73 @@ class [[eosio::contract("force")]] force : public eosio::contract { uint64_t by_account() const { return (uint64_t) account_id; } }; - struct [[eosio::table]] submission { + struct [[eosio::table]] reservation { + // auto incrementing id to ensure task ordering. the id gets + // "bumped" everytime the reservation expires and is refreshed. uint64_t id; + uint32_t task_idx; std::optional account_id; - std::optional content; - checksum256 leaf_hash; - uint64_t batch_id; - std::optional data; - bool paid; - eosio::time_point_sec submitted_on; + uint32_t batch_idx; + eosio::time_point_sec reserved_on; + uint32_t campaign_id; uint64_t primary_key() const { return id; } - checksum256 by_leaf() const { return leaf_hash; } - uint64_t by_batch() const { return batch_id; } - - EOSLIB_SERIALIZE(submission, (id)(account_id)(content)(leaf_hash)(batch_id) - (data)(paid)(submitted_on)) + // index to check if user has a reservation for a + // campaign. account_id in the front, so can be used as account + // filter + uint64_t by_account_campaign() const { return (uint64_t{account_id.value()} << 32) | campaign_id; } + uint64_t by_camp() const { return campaign_id; } + uint64_t by_account() const { return account_id.value(); } }; - struct [[eosio::table]] quali { - uint32_t id; - content content; - uint32_t account_id; - - uint64_t primary_key() const { return uint64_t{id}; } - uint64_t by_account() const { return (uint64_t) account_id; } - }; + struct [[eosio::table]] submission { + uint64_t id; + uint32_t campaign_id; + uint32_t task_idx; + std::optional account_id; + uint32_t batch_idx; + + /** + * The first byte of `data` indicates the type of the submission. + * + * 0 = Empty + * 1 = Raw (IPFS hash, or a UTF-8 encoded string, or different) + * 2+ = ? + * + * Details on the decoding scheme can be found in the campaign JSON. + */ + std::pair> data; + bool paid; + eosio::time_point_sec submitted_on; - struct [[eosio::table]] userquali { - uint32_t account_id; - uint32_t quali_id; - eosio::binary_extension value; + uint64_t primary_key() const { return id; } + uint64_t by_batch() const { return (uint64_t{campaign_id} << 32) | batch_idx; } - uint64_t primary_key() const { return (uint64_t{account_id} << 32) | quali_id; } + EOSLIB_SERIALIZE(submission, (id)(campaign_id)(task_idx)(account_id)(batch_idx) + (data)(paid)(submitted_on)) }; - inline void require_vaccount(uint32_t acc_id, std::vector msg, vaccount::sig sig) { - eosio::name vacc_contract = _config.get().vaccount_contract; + inline void require_vaccount(uint32_t acc_id) { + eosio::name vacc_contract = get_settings().vaccount_contract; vaccount::account_table acc_tbl(vacc_contract, vacc_contract.value); vaccount::account acc = acc_tbl.get((uint64_t) acc_id, "account row not found"); - vaccount::require_auth(msg, acc.address, sig); - }; - - struct [[eosio::table]] config { - eosio::name vaccount_contract; - uint32_t force_vaccount_id; - uint32_t payout_delay_sec; - uint32_t release_task_delay_sec; + vaccount::require_auth(std::vector(), acc.address, std::nullopt); }; - typedef singleton<"config"_n, config> config_table; - typedef multi_index<"campaign"_n, campaign> campaign_table; typedef multi_index<"batch"_n, batch> batch_table; + typedef multi_index<"repsdone"_n, repsdone> repsdone_table; + typedef multi_index<"acctaskidx"_n, acctaskidx> acctaskidx_table; + typedef multi_index< - "batchjoin"_n, - batchjoin, - indexed_by<"accbatch"_n, - const_mem_fun>> - batchjoin_table; + "reservation"_n, reservation, + indexed_by<"acccamp"_n, const_mem_fun>, + indexed_by<"camp"_n, const_mem_fun>, + indexed_by<"acc"_n, const_mem_fun>> + reservation_table; typedef multi_index< "submission"_n, submission, - indexed_by<"leaf"_n, const_mem_fun>, indexed_by<"batch"_n, const_mem_fun>> submission_table; @@ -447,11 +309,6 @@ class [[eosio::contract("force")]] force : public eosio::contract { indexed_by<"acc"_n, const_mem_fun>> payment_table; - typedef multi_index<"quali"_n, quali, - indexed_by<"acc"_n, const_mem_fun>> quali_table; - typedef multi_index<"userquali"_n, userquali> user_quali_table; - - const eosio::name settings_pk = "settings"_n; struct [[eosio::table]] settings { @@ -466,7 +323,6 @@ class [[eosio::contract("force")]] force : public eosio::contract { typedef multi_index<"settings"_n, settings> settings_table; settings_table _settings; - config_table _config; settings get_settings() { auto itr = _settings.find(settings_pk.value); diff --git a/contracts/vaccount/vaccount-shared.hpp b/contracts/vaccount/vaccount-shared.hpp index 31d7e1f..ccc15a2 100644 --- a/contracts/vaccount/vaccount-shared.hpp +++ b/contracts/vaccount/vaccount-shared.hpp @@ -65,4 +65,25 @@ namespace vaccount { "account"_n, account, indexed_by<"token"_n, const_mem_fun>> account_table; + + std::optional get_vaccount(eosio::name vacc_account, uint32_t id) { + account_table acc_tbl(vacc_account, vacc_account.value); + auto acc = acc_tbl.find(id); + if (acc != acc_tbl.end()) + return { *acc }; + return std::nullopt; + } + + bool is_eos(vaddress addr) { + auto addr_type = addr.index(); + return addr_type == 1; + } + + eosio::name get_name(vaddress addr) { + return std::get(addr); + } + + address get_addresss(vaddress addr) { + return std::get
(addr); + } } diff --git a/contracts/vaccount/vaccount.hpp b/contracts/vaccount/vaccount.hpp index dca5a72..3e43036 100644 --- a/contracts/vaccount/vaccount.hpp +++ b/contracts/vaccount/vaccount.hpp @@ -6,7 +6,7 @@ #include #include -#include "../proposals/proposals-shared.hpp" +using namespace eosio; enum class VaccountType { Address = 0, diff --git a/manifest.scm b/manifest.scm index 3b98b14..aa0b689 100644 --- a/manifest.scm +++ b/manifest.scm @@ -1,6 +1,7 @@ -(use-modules (blockchain) - (gnu packages gcc) - (gnu packages node)) +(use-modules + (gnu packages gcc) + (gnu packages node) + (nongnu packages clojure)) (packages->manifest - (list (list gcc "lib") node eosio-cdt gnu-make)) + (list (list gcc "lib") node gnu-make babashka)) diff --git a/tasks/effect.clj b/tasks/effect.clj index 6cf641f..87ca699 100644 --- a/tasks/effect.clj +++ b/tasks/effect.clj @@ -16,12 +16,12 @@ (def rpcs {:jungle4 "https://jungle4.cryptolions.io:443" :mainnet "https://eos.greymass.com"}) -(def wallet-pass (slurp "jungle3-password.txt")) +(def wallet-pass (slurp "jungle4-password.txt")) (declare do-cleos) (def powerup {:jungle4 #(do-cleos :jungle4 "push" "action" "eosio" "powerup" - (str "[\"" payer "\", \"" %1 "\", 1, 100000000000, 100000000000, \"1.0000 EOS\"]") + (str "[\"" payer "\", \"" %1 "\", 1, 50000000000, 50000000000, \"1.0000 EOS\"]") "-p" payer)}) (def deployment @@ -32,7 +32,7 @@ :proposals {:account "efxproposals" :path "contracts/proposals" :hash nil} - :force {:account "efxforce1112" + :force {:account "effecttasks2" :path "contracts/force" :hash nil} :vaccount {:account "efxaccount11" @@ -54,7 +54,7 @@ :dao {:account "theeffectdao" :path "contracts/dao" :hash "22814f2c83433da8e929533e4b46bb3be95bc8826c4e4bcc62242f05b4cd2744"} - :force {:account "force.efx" + :force {:account "tasks.efx" :path "contracts/force" :hash "17e1dab4a77306e236b6f879bb059cd162e97b204e8e530daac8c7666717313b"}}}) @@ -95,8 +95,18 @@ :rows))) (defn get-last-cycle [net] - (let [prop-acc (-> deployment net :proposals :account)] - (-> (cleos net "get" "table" prop-acc prop-acc "cycle" "-l" "1" "-r") + (let [prop-acc (-> deployment net :proposals :account) + cycles + (-> (cleos net "get" "table" prop-acc prop-acc "cycle" "-l" "20" "-r") + :out + (json/decode true) + :rows) + id (->> cycles + (filter #(= (:state %) 1)) + first + :id + inc)] + (-> (cleos net "get" "table" prop-acc prop-acc "cycle" "-U" (str id) "-L" (str id)) :out (json/decode true) :rows @@ -130,8 +140,6 @@ (assoc :actions actions) json/encode))) - - (defn extract-quantity [quantity] (Float/parseFloat (->> quantity (re-seq #"(\d+\.\d+) EFX") first second))) @@ -162,6 +170,41 @@ json/encode (spit filename))) +(defn create-n-cycles + "Creates an action vector that adds `n` consecutive cycle." + [net n out-file-name] + (let [net (keyword net) + + last-cycle (get-last-cycle net) + + config (get-proposal-config net) + + increase-time #(-> % + java.time.LocalDateTime/parse + (.plusSeconds (:cycle_duration_sec config)) + .toString) + + cycle-start-times + (loop [i 0 + start-time (increase-time (:start_time last-cycle)) + times []] + (if (< i (Integer/parseInt n)) + (recur (inc i) + (increase-time start-time) + (conj times start-time )) + times)) + + new-cycle-actions + (map (partial create-cycle-action (keyword net)) cycle-start-times)] + (create-tx-json + (cons + {:account "effecttokens" + :name "open" + :data {:owner "x.efx" :symbol "4,EFX" :ram_payer "x.efx"} + :authorization [{:actor "x.efx" :permission "active"}]} + new-cycle-actions) + out-file-name))) + (defn process-cycle [id] (try (let [net :mainnet @@ -187,7 +230,10 @@ :name "open" :data {:owner "x.efx" :symbol "4,EFX" :ram_payer "x.efx"} :authorization [{:actor "x.efx" :permission "active"}]} - (create-cycle-action net new-cycle-start) + ;; we do not create a new cycle each + ;; time, as it's done in batches now (see + ;; `create-n-cycles`) + ;; (create-cycle-action net new-cycle-start) (transfer-efx-action "daoproposals" (* 0.3 funds-left) "feepool.efx") (transfer-efx-action "daoproposals" (* 0.7 funds-left) "treasury.efx") {:account "daoproposals" @@ -226,7 +272,7 @@ (defn unlock [] (shell "cleos" "wallet" "lock_all") - (shell "cleos" "wallet" "unlock" "-n" "jungle3" "--password" wallet-pass)) + (shell "cleos" "wallet" "unlock" "-n" "jungle4" "--password" wallet-pass)) (defn get-account [net acc] (json/decode (cleos net "get" "account" acc "--json") true)) @@ -473,13 +519,8 @@ (do-cleos net "push" "action" (-> deployment net :force :account) "init" - (str "[" (-> deployment net :vaccount :account) ", 0, 1800, 1800]") - "-p" - (-> deployment net :force :account)) - (do-cleos net "push" "action" - (-> deployment net :force :account) - "migrate" - (str "[" (-> deployment net :force :account) ", " (-> deployment net :feepool :account) ", 0.1]") + (str "[" (-> deployment net :vaccount :account) ", 11, 1800, 1800, " + (-> deployment net :feepool :account) ", 0.1]") "-p" (-> deployment net :force :account)) (do-cleos net "set" "account" "permission" @@ -495,8 +536,8 @@ (-> deployment net :force :account)) (do-cleos net "set" "action" "permission" (-> deployment net :force :account) - (-> deployment net :vaccount :account) - "vtransfer" + (-> deployment net :token :account) + "transfer" "xfer" "-p" (-> deployment net :force :account)) @@ -537,3 +578,20 @@ (str "'" tx "'") proposer "-p" proposer)) (catch Exception e (prn e))))) + +(defn fix-feepool [] + (let [fee-amount (* 0.3 326000) + act + [(transfer-efx-action "daoproposals" fee-amount "feepool.efx") + (transfer-efx-action "daoproposals" (* 0.7 326000) "treasury.efx") + {:account "feepool.efx" + :name "setbalance" + :data {:cycle_id 80 :amount (string/replace (str (format "%.4f" fee-amount)) #"\." "")} + :authorization [{:actor "feepool.efx" :permission "active"}]} + {:account "feepool.efx" + :name "setbalance" + :data {:cycle_id 81 :amount "0"} + :authorization [{:actor "feepool.efx" :permission "active"}]}]] + (create-tx-json + act + "fixfeepool.json"))) diff --git a/tests/atomicassets.abi b/tests/atomicassets.abi new file mode 100644 index 0000000..5ebcf81 --- /dev/null +++ b/tests/atomicassets.abi @@ -0,0 +1,1006 @@ +{ + "version": "eosio::abi/1.1", + "types": [{ + "new_type_name": "ATOMIC_ATTRIBUTE", + "type": "variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC" + },{ + "new_type_name": "ATTRIBUTE_MAP", + "type": "pair_string_ATOMIC_ATTRIBUTE[]" + },{ + "new_type_name": "DOUBLE_VEC", + "type": "float64[]" + },{ + "new_type_name": "FLOAT_VEC", + "type": "float32[]" + },{ + "new_type_name": "INT16_VEC", + "type": "int16[]" + },{ + "new_type_name": "INT32_VEC", + "type": "int32[]" + },{ + "new_type_name": "INT64_VEC", + "type": "int64[]" + },{ + "new_type_name": "INT8_VEC", + "type": "bytes" + },{ + "new_type_name": "STRING_VEC", + "type": "string[]" + },{ + "new_type_name": "UINT16_VEC", + "type": "uint16[]" + },{ + "new_type_name": "UINT32_VEC", + "type": "uint32[]" + },{ + "new_type_name": "UINT64_VEC", + "type": "uint64[]" + },{ + "new_type_name": "UINT8_VEC", + "type": "uint8[]" + } + ], + "structs": [{ + "name": "FORMAT", + "base": "", + "fields": [{ + "name": "name", + "type": "string" + },{ + "name": "type", + "type": "string" + } + ] + },{ + "name": "acceptoffer", + "base": "", + "fields": [{ + "name": "offer_id", + "type": "uint64" + } + ] + },{ + "name": "addcolauth", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "account_to_add", + "type": "name" + } + ] + },{ + "name": "addconftoken", + "base": "", + "fields": [{ + "name": "token_contract", + "type": "name" + },{ + "name": "token_symbol", + "type": "symbol" + } + ] + },{ + "name": "addnotifyacc", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "account_to_add", + "type": "name" + } + ] + },{ + "name": "admincoledit", + "base": "", + "fields": [{ + "name": "collection_format_extension", + "type": "FORMAT[]" + } + ] + },{ + "name": "announcedepo", + "base": "", + "fields": [{ + "name": "owner", + "type": "name" + },{ + "name": "symbol_to_announce", + "type": "symbol" + } + ] + },{ + "name": "assets_s", + "base": "", + "fields": [{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "template_id", + "type": "int32" + },{ + "name": "ram_payer", + "type": "name" + },{ + "name": "backed_tokens", + "type": "asset[]" + },{ + "name": "immutable_serialized_data", + "type": "uint8[]" + },{ + "name": "mutable_serialized_data", + "type": "uint8[]" + } + ] + },{ + "name": "backasset", + "base": "", + "fields": [{ + "name": "payer", + "type": "name" + },{ + "name": "asset_owner", + "type": "name" + },{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "token_to_back", + "type": "asset" + } + ] + },{ + "name": "balances_s", + "base": "", + "fields": [{ + "name": "owner", + "type": "name" + },{ + "name": "quantities", + "type": "asset[]" + } + ] + },{ + "name": "burnasset", + "base": "", + "fields": [{ + "name": "asset_owner", + "type": "name" + },{ + "name": "asset_id", + "type": "uint64" + } + ] + },{ + "name": "canceloffer", + "base": "", + "fields": [{ + "name": "offer_id", + "type": "uint64" + } + ] + },{ + "name": "collections_s", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "author", + "type": "name" + },{ + "name": "allow_notify", + "type": "bool" + },{ + "name": "authorized_accounts", + "type": "name[]" + },{ + "name": "notify_accounts", + "type": "name[]" + },{ + "name": "market_fee", + "type": "float64" + },{ + "name": "serialized_data", + "type": "uint8[]" + } + ] + },{ + "name": "config_s", + "base": "", + "fields": [{ + "name": "asset_counter", + "type": "uint64" + },{ + "name": "template_counter", + "type": "int32" + },{ + "name": "offer_counter", + "type": "uint64" + },{ + "name": "collection_format", + "type": "FORMAT[]" + },{ + "name": "supported_tokens", + "type": "extended_symbol[]" + } + ] + },{ + "name": "createcol", + "base": "", + "fields": [{ + "name": "author", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "allow_notify", + "type": "bool" + },{ + "name": "authorized_accounts", + "type": "name[]" + },{ + "name": "notify_accounts", + "type": "name[]" + },{ + "name": "market_fee", + "type": "float64" + },{ + "name": "data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "createoffer", + "base": "", + "fields": [{ + "name": "sender", + "type": "name" + },{ + "name": "recipient", + "type": "name" + },{ + "name": "sender_asset_ids", + "type": "uint64[]" + },{ + "name": "recipient_asset_ids", + "type": "uint64[]" + },{ + "name": "memo", + "type": "string" + } + ] + },{ + "name": "createschema", + "base": "", + "fields": [{ + "name": "authorized_creator", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "schema_format", + "type": "FORMAT[]" + } + ] + },{ + "name": "createtempl", + "base": "", + "fields": [{ + "name": "authorized_creator", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "transferable", + "type": "bool" + },{ + "name": "burnable", + "type": "bool" + },{ + "name": "max_supply", + "type": "uint32" + },{ + "name": "immutable_data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "declineoffer", + "base": "", + "fields": [{ + "name": "offer_id", + "type": "uint64" + } + ] + },{ + "name": "extended_symbol", + "base": "", + "fields": [{ + "name": "sym", + "type": "symbol" + },{ + "name": "contract", + "type": "name" + } + ] + },{ + "name": "extendschema", + "base": "", + "fields": [{ + "name": "authorized_editor", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "schema_format_extension", + "type": "FORMAT[]" + } + ] + },{ + "name": "forbidnotify", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + } + ] + },{ + "name": "init", + "base": "", + "fields": [] + },{ + "name": "locktemplate", + "base": "", + "fields": [{ + "name": "authorized_editor", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "template_id", + "type": "int32" + } + ] + },{ + "name": "logbackasset", + "base": "", + "fields": [{ + "name": "asset_owner", + "type": "name" + },{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "backed_token", + "type": "asset" + } + ] + },{ + "name": "logburnasset", + "base": "", + "fields": [{ + "name": "asset_owner", + "type": "name" + },{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "template_id", + "type": "int32" + },{ + "name": "backed_tokens", + "type": "asset[]" + },{ + "name": "old_immutable_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "old_mutable_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "asset_ram_payer", + "type": "name" + } + ] + },{ + "name": "logmint", + "base": "", + "fields": [{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "authorized_minter", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "template_id", + "type": "int32" + },{ + "name": "new_asset_owner", + "type": "name" + },{ + "name": "immutable_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "mutable_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "backed_tokens", + "type": "asset[]" + },{ + "name": "immutable_template_data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "lognewoffer", + "base": "", + "fields": [{ + "name": "offer_id", + "type": "uint64" + },{ + "name": "sender", + "type": "name" + },{ + "name": "recipient", + "type": "name" + },{ + "name": "sender_asset_ids", + "type": "uint64[]" + },{ + "name": "recipient_asset_ids", + "type": "uint64[]" + },{ + "name": "memo", + "type": "string" + } + ] + },{ + "name": "lognewtempl", + "base": "", + "fields": [{ + "name": "template_id", + "type": "int32" + },{ + "name": "authorized_creator", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "transferable", + "type": "bool" + },{ + "name": "burnable", + "type": "bool" + },{ + "name": "max_supply", + "type": "uint32" + },{ + "name": "immutable_data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "logsetdata", + "base": "", + "fields": [{ + "name": "asset_owner", + "type": "name" + },{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "old_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "new_data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "logtransfer", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "from", + "type": "name" + },{ + "name": "to", + "type": "name" + },{ + "name": "asset_ids", + "type": "uint64[]" + },{ + "name": "memo", + "type": "string" + } + ] + },{ + "name": "mintasset", + "base": "", + "fields": [{ + "name": "authorized_minter", + "type": "name" + },{ + "name": "collection_name", + "type": "name" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "template_id", + "type": "int32" + },{ + "name": "new_asset_owner", + "type": "name" + },{ + "name": "immutable_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "mutable_data", + "type": "ATTRIBUTE_MAP" + },{ + "name": "tokens_to_back", + "type": "asset[]" + } + ] + },{ + "name": "offers_s", + "base": "", + "fields": [{ + "name": "offer_id", + "type": "uint64" + },{ + "name": "sender", + "type": "name" + },{ + "name": "recipient", + "type": "name" + },{ + "name": "sender_asset_ids", + "type": "uint64[]" + },{ + "name": "recipient_asset_ids", + "type": "uint64[]" + },{ + "name": "memo", + "type": "string" + },{ + "name": "ram_payer", + "type": "name" + } + ] + },{ + "name": "pair_string_ATOMIC_ATTRIBUTE", + "base": "", + "fields": [{ + "name": "key", + "type": "string" + },{ + "name": "value", + "type": "ATOMIC_ATTRIBUTE" + } + ] + },{ + "name": "payofferram", + "base": "", + "fields": [{ + "name": "payer", + "type": "name" + },{ + "name": "offer_id", + "type": "uint64" + } + ] + },{ + "name": "remcolauth", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "account_to_remove", + "type": "name" + } + ] + },{ + "name": "remnotifyacc", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "account_to_remove", + "type": "name" + } + ] + },{ + "name": "schemas_s", + "base": "", + "fields": [{ + "name": "schema_name", + "type": "name" + },{ + "name": "format", + "type": "FORMAT[]" + } + ] + },{ + "name": "setassetdata", + "base": "", + "fields": [{ + "name": "authorized_editor", + "type": "name" + },{ + "name": "asset_owner", + "type": "name" + },{ + "name": "asset_id", + "type": "uint64" + },{ + "name": "new_mutable_data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "setcoldata", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "data", + "type": "ATTRIBUTE_MAP" + } + ] + },{ + "name": "setmarketfee", + "base": "", + "fields": [{ + "name": "collection_name", + "type": "name" + },{ + "name": "market_fee", + "type": "float64" + } + ] + },{ + "name": "setversion", + "base": "", + "fields": [{ + "name": "new_version", + "type": "string" + } + ] + },{ + "name": "templates_s", + "base": "", + "fields": [{ + "name": "template_id", + "type": "int32" + },{ + "name": "schema_name", + "type": "name" + },{ + "name": "transferable", + "type": "bool" + },{ + "name": "burnable", + "type": "bool" + },{ + "name": "max_supply", + "type": "uint32" + },{ + "name": "issued_supply", + "type": "uint32" + },{ + "name": "immutable_serialized_data", + "type": "uint8[]" + } + ] + },{ + "name": "tokenconfigs_s", + "base": "", + "fields": [{ + "name": "standard", + "type": "name" + },{ + "name": "version", + "type": "string" + } + ] + },{ + "name": "transfer", + "base": "", + "fields": [{ + "name": "from", + "type": "name" + },{ + "name": "to", + "type": "name" + },{ + "name": "asset_ids", + "type": "uint64[]" + },{ + "name": "memo", + "type": "string" + } + ] + },{ + "name": "withdraw", + "base": "", + "fields": [{ + "name": "owner", + "type": "name" + },{ + "name": "token_to_withdraw", + "type": "asset" + } + ] + } + ], + "actions": [{ + "name": "acceptoffer", + "type": "acceptoffer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Accept an offer\nsummary: 'The offer with the id {{nowrap offer_id}} is accepted'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe recipient of the offer with the id {{offer_id}} accepts the offer.\n\nThe assets from either side specified in the offer are automatically transferred to the respective other side.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the recipient of the offer.\n
" + },{ + "name": "addcolauth", + "type": "addcolauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Make an account authorized in a collection\nsummary: 'Add the account {{nowrap account_to_add}} to the authorized_accounts list of the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nAdds the account {{account_to_add}} to the authorized_accounts list of the collection {{collection_name}}.\n\nThis allows {{account_to_add}} to both create and edit templates and assets of this collection.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n
" + },{ + "name": "addconftoken", + "type": "addconftoken", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Add token to supported list\nsummary: 'Adds a token that can then be used to back assets'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\nDescription:\n
\nThe token with the symbol {{token_symbol}} from the token contract {{token_contract}} is added to the supported_tokens list.\n\nThis means that assets can then be backed with that specific token.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{$action.account}}.\n
" + },{ + "name": "addnotifyacc", + "type": "addnotifyacc", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Add an account to a collection's notify list\nsummary: 'Add the account {{nowrap account_to_add}} to the notify_accounts list of the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nAdds the account {{account_to_add}} to the notify_accounts list of the collection {{collection_name}}.\n\nThis will make {{account_to_add}} get notifications directly on the blockchain when one of the following actions is performed:\n- One or more assets of the collection {{collection_name}} is transferred\n- An asset of the collection {{collection_name}} is minted\n- An asset of the collection {{collection_name}} has its mutable data changed\n- An asset of the collection {{collection_name}} is burned\n- An asset of the collection {{collection_name}} gets backed with core tokens\n- A template of the collection {{collection_name}} is created\n\n{{account_to_add}} is able to add code to their own smart contract to handle these notifications. \n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n\n{{account_to_add}} may not make any transactions throw when receiving a notification. This includes, but is not limited to, purposely blocking certain transfers by making the transaction throw.\n\nIt is the collection author's responsibility to enforce that this does not happen.\n
" + },{ + "name": "admincoledit", + "type": "admincoledit", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Extend collections schema\nsummary: 'Extends the schema to serialize collection data by one or more lines'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe following FORMAT lines are added to the schema that is used to serialize collections data:\n{{#each collection_format_extension}}\n - name: {{this.name}} , type: {{this.type}}\n{{/each}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{$action.account}}.\n
" + },{ + "name": "announcedepo", + "type": "announcedepo", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Announces a deposit\nsummary: '{{nowrap owner}} adds the symbol {{nowrap symbol_to_announce}} to his balance table row'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThis action is used to add a zero value asset to the quantities vector of the balance row with the owner {{owner}}.\nIf there is no balance row with the owner {{owner}}, a new one is created.\nAdding something to a vector increases the RAM required, therefore this can't be done directly in the receipt of the transfer action, so using this action a zero value is added so that the RAM required doesn't change when adding the received quantity in the transfer action later.\n\nBy calling this action, {{payer}} pays for the RAM of the balance table row with the owner {{owner}}.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{payer}}.\n
" + },{ + "name": "backasset", + "type": "backasset", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Backs an asset with tokens\nsummary: '{{nowrap payer}} backs the asset with the ID {{nowrap asset_id}} with {{nowrap token_to_back}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{payer}} backs an the asset with the ID {{asset_id}} owned by {{asset_owner}} with {{token_to_back}}.\n{{payer}} must have at least as many tokens in his balance. {{token_to_back}} will be removed from {{payer}}'s balance.\nThe tokens backed to this asset can be retreived by burning the asset, in which case the owner at the time of the burn will receive the tokens.\n\n{{payer}} pays for the full RAM cost of the asset.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{payer}}.\n
" + },{ + "name": "burnasset", + "type": "burnasset", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Burn an asset\nsummary: '{{nowrap asset_owner}} burns his asset with the id {{nowrap asset_id}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{asset_owner}} burns his asset with the id {{asset_id}}.\n\nIf there previously were core tokens backed for this asset, these core tokens are transferred to {{asset_owner}}.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{asset_owner}}.\n
" + },{ + "name": "canceloffer", + "type": "canceloffer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Cancel an offer\nsummary: 'The offer with the id {{nowrap offer_id}} is cancelled'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe creator of the offer with the id {{offer_id}} cancels this offer. The offer is deleted from the offers table.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the creator of the offer.\n
" + },{ + "name": "createcol", + "type": "createcol", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create collection\nsummary: '{{nowrap author}} creates a new collection with the name {{collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{author}} creates a new collection with the name {{collection_name}}.\n\n{{#if authorized_accounts}}The following accounts are added to the authorized_accounts list, allowing them create and edit templates and assets within this collection:\n {{#each authorized_accounts}}\n - {{this}}\n {{/each}}\n{{else}}No accounts are added to the authorized_accounts list.\n{{/if}}\n\n{{#if notify_accounts}}The following accounts are added to the notify_accounts list, which means that they get notified on the blockchain of any actions related to assets and templates of this collection:\n {{#each notify_accounts}}\n - {{this}}\n {{/each}}\n{{else}}No accounts are added to the notify_accounts list.\n{{/if}}\n\n{{#if allow_notify}}It will be possible to add more accounts to the notify_accounts list later.\n{{else}}It will not be possible to add more accounts to the notify_accounts list later.\n{{/if}}\n\nThe market_fee for this collection will be set to {{market_fee}}. 3rd party markets are encouraged to use this value to collect fees for the collection author, but are not required to do so.\n\n{{#if data}}The collections will be initialized with the following data:\n {{#each data}}\n - name: {{this.key}} , value: {{this.value}}\n {{/each}}\n{{else}}The collection will be initialized without any data.\n{{/if}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{author}}.\n\nCreating collections with the purpose of confusing or taking advantage of others, especially by impersonating other well known brands, personalities or dapps is not allowed.\n\nIf the notify functionality is being used, the notify accounts may not make any transactions throw when receiving the notification. This includes, but is not limited to, purposely blocking certain transfers by making the transaction throw.\n\nIt is the collection author's responsibility to enforce that this does not happen.\n
" + },{ + "name": "createoffer", + "type": "createoffer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create an offer\nsummary: '{{nowrap sender}} makes an offer to {{nowrap recipient}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{sender}} makes the following offer to {{recipient}}.\n\n{{#if sender_asset_ids}}{{sender}} gives the assets with the following ids:\n {{#each sender_asset_ids}}\n - {{this}}\n {{/each}}\n{{else}}{{sender}} does not give any assets.\n{{/if}}\n\n{{#if recipient_asset_ids}}{{recipient}} gives the assets with the following ids:\n {{#each recipient_asset_ids}}\n - {{this}}\n {{/each}}\n{{else}}{{recipient}} does not give any assets.\n{{/if}}\n\nIf {{recipient}} accepts the offer, the assets will automatically be transferred to the respective sides.\n\n{{#if memo}}There is a memo attached to the offer stating:\n {{memo}}\n{{else}}No memo is attached to the offer.\n{{/if}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{sender}}.\n\nCreating offers that do not serve any purpose other than spamming the recipient is not allowed.\n\n{{sender}} must not take advantage of the notification they receive when the offer is accepted or declined in a way that harms {{recipient}}.\n
" + },{ + "name": "createschema", + "type": "createschema", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a schema\nsummary: '{{nowrap authorized_creator}} creates a new schema with the name {{nowrap schema_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{authorized_creator}} creates a new schema with the name {{schema_name}}. This schema belongs to the collection {{collection_name}}\n\n{{#if schema_format}}The schema will be initialized with the following FORMAT lines that can be used to serialize template and asset data:\n {{#each schema_format}}\n - name: {{this.name}} , type: {{this.type}}\n {{/each}}\n{{else}}The schema will be initialized without any FORMAT lines.\n{{/if}}\n\nOnly authorized accounts of the {{collection_name}} collection will be able to extend the schema by adding additional FORMAT lines in the future, but they will not be able to delete previously added FORMAT lines.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{authorized_creator}}.\n\n{{authorized_creator}} has to be an authorized account in the collection {{collection_name}}.\n\nCreating schemas with the purpose of confusing or taking advantage of others, especially by impersonating other well known brands, personalities or dapps is not allowed.\n
" + },{ + "name": "createtempl", + "type": "createtempl", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Create a template\nsummary: '{{nowrap authorized_creator}} creates a new template which belongs to the {{nowrap collection_name}} collection and uses the {{nowrap schema_name}} schema'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{authorized_creator}} creates a new template which belongs to the {{collection_name}} collection.\n\nThe schema {{schema_name}} is used for the serialization of the template's data.\n\n{{#if transferable}}The assets within this template will be transferable\n{{else}}The assets within this template will not be transferable\n{{/if}}\n\n{{#if burnable}}The assets within this template will be burnable\n{{else}}The assets within this template will not be burnable\n{{/if}}\n\n{{#if max_supply}}A maximum of {{max_supply}} assets can ever be created within this template.\n{{else}}There is no maximum amount of assets that can be created within this template.\n{{/if}}\n\n{{#if immutable_data}}The immutable data of the template is set to:\n {{#each immutable_data}}\n - name: {{this.key}} , value: {{this.value}}\n {{/each}}\n{{else}}No immutable data is set for the template.\n{{/if}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{authorized_creator}}.\n\n{{authorized_creator}} has to be an authorized account in the collection {{collection_name}}.\n
" + },{ + "name": "declineoffer", + "type": "declineoffer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Decline an offer\nsummary: 'The offer with the id {{nowrap offer_id}} is declined'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe recipient of the offer with the id {{offer_id}} declines the offer. The offer is deleted from the offers table.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the recipient of the offer.\n
" + },{ + "name": "extendschema", + "type": "extendschema", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Extend schema\nsummary: 'Extends the schema {{nowrap schema_name}} by adding one or more FORMAT lines'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe schema {{schema_name}} belonging to the collection {{collection_name}} is extended by adding the following FORMAT lines that can be used to serialize template and asset data:\n{{#each schema_format_extension}}\n - name: {{this.name}} , type: {{this.type}}\n{{/each}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{authorized_editor}}.\n\n{{authorized_editor}} has to be an authorized account in the collection {{collection_name}}.\n
" + },{ + "name": "forbidnotify", + "type": "forbidnotify", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Disallow collection notifications\nsummary: 'Sets the allow_notify value of the collection {{nowrap collection_name}} to false'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe allow_notify value of the collection {{collection_name}} is set to false.\nThis means that it will not be possible to add accounts to the notify_accounts list later.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n
" + },{ + "name": "init", + "type": "init", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Initialize config tables\nsummary: 'Initialize the tables \"config\" and \"tokenconfig\" if they have not been initialized before'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nInitialize the tables \"config\" and \"tokenconfig\" if they have not been initialized before. If they have been initialized before, nothing will happen.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{$action.account}}.\n
" + },{ + "name": "locktemplate", + "type": "locktemplate", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Locks a template\nsummary: '{{nowrap authorized_editor}} locks the template with the id {{nowrap template_id}} belonging to the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{authorized_editor}} locks the template with the id {{template_id}} belonging to the collection {{collection_name}}.\n\nThis sets the template's maximum supply to the template's current supply, which means that no more assets referencing this template can be minted.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{authorized_creator}}.\n\n{{authorized_creator}} has to be an authorized account in the collection {{collection_name}}.\n\nThe template's issued supply must be greater than 0.\n
" + },{ + "name": "logbackasset", + "type": "logbackasset", + "ricardian_contract": "" + },{ + "name": "logburnasset", + "type": "logburnasset", + "ricardian_contract": "" + },{ + "name": "logmint", + "type": "logmint", + "ricardian_contract": "" + },{ + "name": "lognewoffer", + "type": "lognewoffer", + "ricardian_contract": "" + },{ + "name": "lognewtempl", + "type": "lognewtempl", + "ricardian_contract": "" + },{ + "name": "logsetdata", + "type": "logsetdata", + "ricardian_contract": "" + },{ + "name": "logtransfer", + "type": "logtransfer", + "ricardian_contract": "" + },{ + "name": "mintasset", + "type": "mintasset", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Mint an asset\nsummary: '{{nowrap authorized_minter}} mints an asset which will be owned by {{nowrap new_asset_owner}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{authorized_minter}} mints an asset of the template which belongs to the {{schema_name}} schema of the {{collection_name}} collection. The asset will be owned by {{new_asset_owner}}.\n\n{{#if immutable_data}}The immutable data of the asset is set to:\n {{#each immutable_data}}\n - name: {{this.key}} , value: {{this.value}}\n {{/each}}\n{{else}}No immutable data is set for the asset.\n{{/if}}\n\n{{#if mutable_data}}The mutable data of the asset is set to:\n {{#each mutable_data}}\n - name: {{this.key}} , value: {{this.value}}\n {{/each}}\n{{else}}No mutable data is set for the asset.\n{{/if}}\n\n{{#if quantities_to_back}}The asset will be backed with the following tokens and {{authorized_minter}} needs to have at least that amount of tokens in their balance:\n {{#each quantities_to_back}}\n - {{quantities_to_back}}\n {{/each}}\n{{/if}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{authorized_minter}}.\n\n{{authorized_minter}} has to be an authorized account in the collection that the template with the id {{template_id}} belongs to.\n\nMinting assets that contain intellectual property requires the permission of the all rights holders of that intellectual property.\n\nMinting assets with the purpose of confusing or taking advantage of others, especially by impersonating other well known brands, personalities or dapps is not allowed.\n\nMinting assets with the purpose of spamming or otherwise negatively impacing {{new_owner}} is not allowed.\n
" + },{ + "name": "payofferram", + "type": "payofferram", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Pays RAM for existing offer\nsummary: '{{nowrap payer}} will pay for the RAM cost of the offer {{nowrap offer_id}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{payer}} pays for the RAM cost of the offer {{offer_id}}. The offer itself is not modified\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{payer}}.\n
" + },{ + "name": "remcolauth", + "type": "remcolauth", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove an account's authorization in a collection\nsummary: 'Remove the account {{nowrap account_to_remove}} from the authorized_accounts list of the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nRemoves the account {{account_to_remove}} from the authorized_accounts list of the collection {{collection_name}}.\n\nThis removes {{account_to_remove}}'s permission to both create and edit templates and assets of this collection.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n
" + },{ + "name": "remnotifyacc", + "type": "remnotifyacc", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Remove an account from a collection's notfiy list\nsummary: 'Remove the account {{nowrap account_to_remove}} from the notify_accounts list of the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nRemoves the account {{account_to_remove}} from the notify_accounts list of the collection {{collection_name}}.\n\n{{account_to_remove}} will therefore no longer receive notifications for any of the actions related to the collection {{collection_name}}.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n
" + },{ + "name": "setassetdata", + "type": "setassetdata", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set the mutable data of an asset\nsummary: '{{nowrap authorized_editor}} sets the mutable data of the asset with the id {{nowrap asset_id}} owned by {{nowrap asset_owner}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{#if new_mutable_data}}{{authorized_editor}} sets the mutable data of the asset with the id {{asset_id}} owned by {{nowrap asset_owner}} to the following:\n {{#each new_mutable_data}}\n - name: {{this.key}} , value: {{this.value}}\n {{/each}}\n{{else}}{{authorized_editor}} clears the mutable data of the asset with the id {{asset_id}} owned by {{asset_owner}}.\n{{/if}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{authorized_editor}}.\n\n{{authorized_editor}} has to be an authorized account in the collection that the asset with the id {{asset_id}} belongs to. (An asset belongs to the collection that the template it is within belongs to)\n
" + },{ + "name": "setcoldata", + "type": "setcoldata", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set collection data\nsummary: 'Sets the data of the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{#if data}}Sets the data of the collection {{collection_name}} to the following\n {{#each data}}\n - name: {{this.key}} , value: {{this.value}}\n {{/each}}\n{{else}}Clears the data of the collection {{collection_name}}\n{{/if}}\n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n
" + },{ + "name": "setmarketfee", + "type": "setmarketfee", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set collection market fee\nsummary: 'Sets the market fee of the collection {{nowrap collection_name}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\nThe market_fee for the collection {{collection_name}} will be set to {{market_fee}}. 3rd party markets are encouraged to use this value to collect fees for the collection author, but are not required to do so.\n
\n\nClauses:\n
\nThis action may only be called with the permission of the collection's author.\n
" + },{ + "name": "setversion", + "type": "setversion", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Set tokenconfig version\nsummary: 'Sets the version in the tokenconfigs table to {{nowrap new_version}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\nDescription:\n
\nThe version in the tokenconfigs table is set to {{new_version}}.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{$action.account}}.\n
" + },{ + "name": "transfer", + "type": "transfer", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Transfer Assets\nsummary: 'Send one or more assets from {{nowrap from}} to {{nowrap to}}'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{from}} transfers one or more assets with the following ids to {{to}}:\n{{#each asset_ids}}\n - {{this}}\n{{/each}}\n\n{{#if memo}}There is a memo attached to the transfer stating:\n {{memo}}\n{{else}}No memo is attached to the transfer.\n{{/if}}\n\nIf {{to}} does not own any assets, {{from}} pays the RAM for the scope of {{to}} in the assets table.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{from}}.\n\nTransfers that do not serve any purpose other than spamming the recipient are not allowed.\n
" + },{ + "name": "withdraw", + "type": "withdraw", + "ricardian_contract": "---\nspec_version: \"0.2.0\"\ntitle: Withdraws fungible tokens\nsummary: '{{nowrap owner}} withdraws {{token_to_withdraw}} from his balance'\nicon: https://atomicassets.io/image/logo256.png#108AEE3530F4EB368A4B0C28800894CFBABF46534F48345BF6453090554C52D5\n---\n\nDescription:\n
\n{{owner}} withdraws {{token_to_withdraw}} that they previously deposited and have not yet spent otherwise.\nThe tokens will be transferred back to {{owner}} and will be deducted from {{owner}}'s balance.\n
\n\nClauses:\n
\nThis action may only be called with the permission of {{owner}}.\n
" + } + ], + "tables": [{ + "name": "assets", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "assets_s" + },{ + "name": "balances", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "balances_s" + },{ + "name": "collections", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "collections_s" + },{ + "name": "config", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "config_s" + },{ + "name": "offers", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "offers_s" + },{ + "name": "schemas", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "schemas_s" + },{ + "name": "templates", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "templates_s" + },{ + "name": "tokenconfigs", + "index_type": "i64", + "key_names": [], + "key_types": [], + "type": "tokenconfigs_s" + } + ], + "ricardian_clauses": [], + "error_messages": [], + "abi_extensions": [], + "variants": [{ + "name": "variant_int8_int16_int32_int64_uint8_uint16_uint32_uint64_float32_float64_string_INT8_VEC_INT16_VEC_INT32_VEC_INT64_VEC_UINT8_VEC_UINT16_VEC_UINT32_VEC_UINT64_VEC_FLOAT_VEC_DOUBLE_VEC_STRING_VEC", + "types": [ + "int8", + "int16", + "int32", + "int64", + "uint8", + "uint16", + "uint32", + "uint64", + "float32", + "float64", + "string", + "INT8_VEC", + "INT16_VEC", + "INT32_VEC", + "INT64_VEC", + "UINT8_VEC", + "UINT16_VEC", + "UINT32_VEC", + "UINT64_VEC", + "FLOAT_VEC", + "DOUBLE_VEC", + "STRING_VEC" + ] + } + ], + "action_results": [] +} \ No newline at end of file diff --git a/tests/atomicassets.wasm b/tests/atomicassets.wasm new file mode 100644 index 0000000..5b4661f Binary files /dev/null and b/tests/atomicassets.wasm differ diff --git a/tests/e2e/dao.cljs b/tests/e2e/dao.cljs index 26f01cc..aa501ce 100644 --- a/tests/e2e/dao.cljs +++ b/tests/e2e/dao.cljs @@ -6,7 +6,8 @@ [cljs.core.async :refer [go] ] [cljs.core.async.interop :refer [addr vacc/keypair-pub)] - ["name" acc-3] - ["name" acc-2] - ["name" acc-4] - ["name" acc-5]]) + ["name" acc-3] ;; vaccount id = 1 + ["name" acc-2] ;; + ["name" acc-4] ;; vaccount id = 3 + ["name" acc-5]]) ;; vaccount-id = 4 (def force-vacc-id 5) +(def atomic-acc "atomicassets") +(def force-collection-name "forcecollect") + (use-fixtures :once {:before (fn [] @@ -71,6 +75,7 @@ (try (hex @@ -179,36 +208,10 @@ (doto (new (.-SerialBuffer Serialize)) (.push mark) (.pushNumberAsUint64 task-id)(.pushUint32 acc-id)))) -(defn pack-mkbatch-params [id camp-id content root] +(defn pack-mkbatch-params [id camp-id content] (.asUint8Array (doto (new (.-SerialBuffer Serialize)) - (.push 8) (.pushUint32 id) (.pushUint32 camp-id) (.push 0) (.pushString content) - (.pushUint8ArrayChecked (vacc/hex->bytes root) 32)))) - -(defn pack-mkquali-params [acc-id content] - (.asUint8Array - (doto (new (.-SerialBuffer Serialize)) - (.push 18) (.pushUint32 acc-id) (.push 0) (.pushString content)))) - -(defn pack-editquali-params [acc-id content] - (.asUint8Array - (doto (new (.-SerialBuffer Serialize)) - (.push 20) (.pushUint32 acc-id) (.push 0) (.pushString content)))) - -(defn pack-mkquali-params [acc-id content] - (.asUint8Array - (doto (new (.-SerialBuffer Serialize)) - (.push 18) (.pushUint32 acc-id) (.push 0) (.pushString content)))) - -(defn pack-assignquali-params [id user-id value] - (.asUint8Array - (doto (new (.-SerialBuffer Serialize)) - (.push 19) (.pushUint32 id) (.pushUint32 user-id) (.pushString value)))) - -(defn pack-uassignquali-params [id user-id] - (.asUint8Array - (doto (new (.-SerialBuffer Serialize)) - (.push 20) (.pushUint32 id) (.pushUint32 user-id)))) + (.push 8) (.pushUint32 id) (.pushUint32 camp-id) (.push 0) (.pushString content)))) (defn pack-rmbatch-params [id camp-id] (.asUint8Array @@ -227,12 +230,6 @@ (.asUint8Array (doto (new (.-SerialBuffer Serialize)) (.push 13) (.pushUint32 acc-id)))) -(defn pack-reservetask-params [leaf-hash camp-id batch-id] - (.asUint8Array - (doto (new (.-SerialBuffer Serialize)) - (.push 6) (.pushUint8ArrayChecked (vacc/hex->bytes leaf-hash) 32) - (.pushUint32 camp-id) (.pushUint32 batch-id)))) - (defn pack-submittask-params [sub-id data] (.asUint8Array (doto (new (.-SerialBuffer Serialize)) @@ -252,6 +249,7 @@ (hex (.digest (.update (.hash ec) data)))) -(async-deftest closebatch +#_(async-deftest closebatch (let [params (pack-closebatch-params (get-composite-key 0 5))] - (testing "campaign owner can pause batch"x - (is (= (bytes %)) task-data-prep) - tree (MerkleTree. (clj->js leaves) sha256) - root (.toString (.getRoot tree) "hex") - ;; _ (prn "merkle root for " batch-pk root) - params-1 (pack-reservetask-params (first leaves) 0 0) - params-2 (pack-reservetask-params (second leaves) 0 0)] - (for [i (range 2)] - (let [proof (.getProof tree (nth leaves i))] - [(map #(buf->hex (.-data %)) proof) - (map #(if (= (.-position %) "left") 0 1) proof) - (nth leaves i)])))) + (async-deftest reservetask - (let [[proof-000 proof-001 & _] (make-proof "0000000000000000") - [proof-200 & _] (make-proof "0200000000000000") - [proof-020 & _] (make-proof "0000000002000000")] - (testing "can make reservation" + (testing "user 1 makes reservation" + (js/console.log + (eos/tx-get-console (> rows (filter #(= (% "id") 0)) first)] - (is (= (get-in r ["vote_counts" 0 "value"]) 0)) - (is (= (get-in r ["vote_counts" 1 "value"]) 24042)) - (is (= (get-in r ["vote_counts" 2 "value"]) 37276))) + (is (= (get-in r ["vote_counts" 0 "second"]) 0)) + (is (= (get-in r ["vote_counts" 1 "second"]) 24042)) + (is (= (get-in r ["vote_counts" 2 "second"]) 37276))) (