Compare commits
No commits in common. "4f68bc964b124829adb6a37a55b7f183edfdbffa" and "ef72a5650176b6695595f6c947ad1b96e32b5762" have entirely different histories.
4f68bc964b
...
ef72a56501
11 changed files with 28 additions and 600 deletions
|
|
@ -8,10 +8,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
verify:
|
verify:
|
||||||
runs-on: docker
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: https://github.com/actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
with:
|
with:
|
||||||
distribution: temurin
|
distribution: temurin
|
||||||
java-version: "25"
|
java-version: "25"
|
||||||
|
|
@ -19,7 +19,7 @@ jobs:
|
||||||
publish:
|
publish:
|
||||||
if: startsWith(gitea.ref, 'refs/tags/v')
|
if: startsWith(gitea.ref, 'refs/tags/v')
|
||||||
needs: verify
|
needs: verify
|
||||||
runs-on: docker
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
- uses: actions/setup-java@v4
|
- uses: actions/setup-java@v4
|
||||||
|
|
|
||||||
|
|
@ -58,7 +58,6 @@ enum class OpCode(val code: Byte) {
|
||||||
data class Instruction(val opCode: OpCode, val immediate: ByteArray = byteArrayOf()) {
|
data class Instruction(val opCode: OpCode, val immediate: ByteArray = byteArrayOf()) {
|
||||||
init {
|
init {
|
||||||
require(opCode != OpCode.PUSH32 || immediate.size == 32)
|
require(opCode != OpCode.PUSH32 || immediate.size == 32)
|
||||||
require(opCode == OpCode.PUSH32 || immediate.isEmpty())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,10 +78,8 @@ object TwistBytecode {
|
||||||
while (input.hasRemaining()) {
|
while (input.hasRemaining()) {
|
||||||
val code = input.get()
|
val code = input.get()
|
||||||
val op = OpCode.entries.firstOrNull { it.code == code } ?: error("Unknown opcode")
|
val op = OpCode.entries.firstOrNull { it.code == code } ?: error("Unknown opcode")
|
||||||
val immediate = if (op == OpCode.PUSH32) {
|
val immediate =
|
||||||
require(input.remaining() >= 32) { "Truncated PUSH32 immediate" }
|
if (op == OpCode.PUSH32) ByteArray(32).also(input::get) else byteArrayOf()
|
||||||
ByteArray(32).also(input::get)
|
|
||||||
} else byteArrayOf()
|
|
||||||
output += Instruction(op, immediate)
|
output += Instruction(op, immediate)
|
||||||
}
|
}
|
||||||
return output
|
return output
|
||||||
|
|
|
||||||
|
|
@ -43,49 +43,7 @@ data class ContractIr(
|
||||||
val functions: List<FunctionIr>,
|
val functions: List<FunctionIr>,
|
||||||
val accessPolicy: IrAccessPolicy = IrAccessPolicy(),
|
val accessPolicy: IrAccessPolicy = IrAccessPolicy(),
|
||||||
) {
|
) {
|
||||||
/**
|
fun bytecode(): ByteArray = TwistBytecode.encode(functions.flatMap { it.instructions })
|
||||||
* Encodes a deployed contract. Calldata word zero is the function selector and all
|
|
||||||
* function arguments begin at word one. A non-matching selector deterministically reverts.
|
|
||||||
*/
|
|
||||||
fun bytecode(): ByteArray {
|
|
||||||
require(functions.map { it.selector }.distinct().size == functions.size) { "Duplicate function selector" }
|
|
||||||
val dispatchSize = 2 + functions.size * 5 + 1
|
|
||||||
var nextBody = dispatchSize
|
|
||||||
val targets = functions.associate { function ->
|
|
||||||
function.selector to nextBody.also { nextBody += function.instructions.size }
|
|
||||||
}
|
|
||||||
val output = mutableListOf(
|
|
||||||
Instruction(OpCode.PUSH32, Word256.ZERO.toBytes()),
|
|
||||||
Instruction(OpCode.CALLDATA_LOAD),
|
|
||||||
)
|
|
||||||
functions.forEach { function ->
|
|
||||||
output += Instruction(OpCode.DUP)
|
|
||||||
output += Instruction(OpCode.PUSH32, Word256.fromLong(function.selector.toLong()).toBytes())
|
|
||||||
output += Instruction(OpCode.EQ)
|
|
||||||
output += Instruction(OpCode.PUSH32, Word256.fromLong(targets.getValue(function.selector).toLong()).toBytes())
|
|
||||||
output += Instruction(OpCode.JUMPI)
|
|
||||||
}
|
|
||||||
output += Instruction(OpCode.REVERT)
|
|
||||||
functions.forEach { function ->
|
|
||||||
val offset = targets.getValue(function.selector)
|
|
||||||
output += function.instructions.mapIndexed { index, instruction ->
|
|
||||||
if (
|
|
||||||
instruction.opCode == OpCode.PUSH32 &&
|
|
||||||
function.instructions.getOrNull(index + 1)?.opCode in setOf(OpCode.JUMP, OpCode.JUMPI)
|
|
||||||
) {
|
|
||||||
Instruction(
|
|
||||||
OpCode.PUSH32,
|
|
||||||
Word256.of(
|
|
||||||
Word256.fromBytes(instruction.immediate).toBigInteger() +
|
|
||||||
java.math.BigInteger.valueOf(offset.toLong())
|
|
||||||
)
|
|
||||||
.toBytes(),
|
|
||||||
)
|
|
||||||
} else instruction
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return TwistBytecode.encode(output)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
data class FunctionIr(val name: String, val selector: Int, val instructions: List<Instruction>)
|
data class FunctionIr(val name: String, val selector: Int, val instructions: List<Instruction>)
|
||||||
|
|
@ -125,34 +83,6 @@ class FunctionBuilder {
|
||||||
|
|
||||||
fun callValue() = op(OpCode.CALLVALUE)
|
fun callValue() = op(OpCode.CALLVALUE)
|
||||||
|
|
||||||
fun add() = op(OpCode.ADD)
|
|
||||||
|
|
||||||
fun subtract() = op(OpCode.SUB)
|
|
||||||
|
|
||||||
fun multiply() = op(OpCode.MUL)
|
|
||||||
|
|
||||||
fun divide() = op(OpCode.DIV)
|
|
||||||
|
|
||||||
fun equal() = op(OpCode.EQ)
|
|
||||||
|
|
||||||
fun lessThan() = op(OpCode.LT)
|
|
||||||
|
|
||||||
fun greaterThan() = op(OpCode.GT)
|
|
||||||
|
|
||||||
fun jump() = op(OpCode.JUMP)
|
|
||||||
|
|
||||||
fun jumpIf() = op(OpCode.JUMPI)
|
|
||||||
|
|
||||||
fun duplicate() = op(OpCode.DUP)
|
|
||||||
|
|
||||||
fun discard() = op(OpCode.POP)
|
|
||||||
|
|
||||||
fun calldataSize() = op(OpCode.CALLDATA_SIZE)
|
|
||||||
|
|
||||||
fun codeSize() = op(OpCode.CODE_SIZE)
|
|
||||||
|
|
||||||
fun gasRemaining() = op(OpCode.GAS)
|
|
||||||
|
|
||||||
fun emitLog() = op(OpCode.LOG)
|
fun emitLog() = op(OpCode.LOG)
|
||||||
|
|
||||||
fun returnWord() = op(OpCode.RETURN)
|
fun returnWord() = op(OpCode.RETURN)
|
||||||
|
|
|
||||||
|
|
@ -1,285 +0,0 @@
|
||||||
package rip.crit.twist.compiler
|
|
||||||
|
|
||||||
import java.math.BigInteger
|
|
||||||
import rip.crit.twist.bytecode.Instruction
|
|
||||||
import rip.crit.twist.bytecode.OpCode
|
|
||||||
import rip.crit.twist.jit.LLVMBuilder
|
|
||||||
import rip.crit.twist.tvm.Word256
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A compact ML-inspired source language for contracts.
|
|
||||||
*
|
|
||||||
* Functions take 256-bit calldata words, `let` is lexical, and `load` and `store` are
|
|
||||||
* explicit state effects. It deliberately has no implicit conversions or host calls.
|
|
||||||
*/
|
|
||||||
class TwistMlCompiler {
|
|
||||||
fun parse(source: String): TwistMlContract = Parser(source).contract()
|
|
||||||
|
|
||||||
fun compile(source: String): ContractIr = compile(parse(source))
|
|
||||||
|
|
||||||
fun compile(contract: TwistMlContract): ContractIr =
|
|
||||||
ContractIr(
|
|
||||||
contract.name,
|
|
||||||
contract.functions.mapIndexed { index, function ->
|
|
||||||
FunctionIr(function.name, index + 1, Lowerer(function.parameters).lower(function.body))
|
|
||||||
},
|
|
||||||
)
|
|
||||||
|
|
||||||
/** Lowers pure arithmetic and explicit storage effects to portable LLVM IR. */
|
|
||||||
fun lowerToLlvm(source: String, moduleName: String = "twistml"): LLVMBuilder {
|
|
||||||
val contract = parse(source)
|
|
||||||
return LLVMBuilder(moduleName, LlvmLowerer(contract).lower())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class LlvmLowerer(private val contract: TwistMlContract) {
|
|
||||||
private var value = 0
|
|
||||||
private var label = 0
|
|
||||||
private val lines = mutableListOf<String>()
|
|
||||||
private var needsLoad = false
|
|
||||||
private var needsStore = false
|
|
||||||
private var needsCaller = false
|
|
||||||
private var needsCallValue = false
|
|
||||||
|
|
||||||
fun lower(): String {
|
|
||||||
contract.functions.forEach { function -> lowerFunction(function) }
|
|
||||||
return buildString {
|
|
||||||
append("target triple = \"unknown-unknown-unknown\"\n")
|
|
||||||
if (needsLoad) append("declare i256 @twist_sload(i256)\n")
|
|
||||||
if (needsStore) append("declare void @twist_sstore(i256, i256)\n")
|
|
||||||
if (needsCaller) append("declare i256 @twist_caller()\n")
|
|
||||||
if (needsCallValue) append("declare i256 @twist_callvalue()\n")
|
|
||||||
append(lines.joinToString("\n"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun lowerFunction(function: TwistMlFunction) {
|
|
||||||
value = 0
|
|
||||||
label = 0
|
|
||||||
val arguments = function.parameters.mapIndexed { index, _ -> "i256 %arg$index" }
|
|
||||||
lines += "define i256 @${function.name}(" + arguments.joinToString(", ") + ") {"
|
|
||||||
lines += "entry:"
|
|
||||||
val environment = function.parameters.mapIndexed { index, name -> name to "%arg$index" }.toMap()
|
|
||||||
val result = emit(function.body, environment)
|
|
||||||
lines += " ret i256 $result"
|
|
||||||
lines += "}"
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun emit(expression: TwistMlExpr, environment: Map<String, String>): String = when (expression) {
|
|
||||||
is TwistMlExpr.Number -> expression.value.toString()
|
|
||||||
is TwistMlExpr.Variable -> environment[expression.name] ?: error("Unknown variable ${expression.name}")
|
|
||||||
is TwistMlExpr.Let -> emit(expression.body, environment + (expression.name to emit(expression.value, environment)))
|
|
||||||
TwistMlExpr.Caller -> {
|
|
||||||
needsCaller = true
|
|
||||||
call("i256 @twist_caller()")
|
|
||||||
}
|
|
||||||
TwistMlExpr.CallValue -> {
|
|
||||||
needsCallValue = true
|
|
||||||
call("i256 @twist_callvalue()")
|
|
||||||
}
|
|
||||||
is TwistMlExpr.Load -> {
|
|
||||||
needsLoad = true
|
|
||||||
call("i256 @twist_sload(${emit(expression.key, environment)})")
|
|
||||||
}
|
|
||||||
is TwistMlExpr.Store -> {
|
|
||||||
needsStore = true
|
|
||||||
lines += " call void @twist_sstore(i256 ${emit(expression.key, environment)}, i256 ${emit(expression.value, environment)})"
|
|
||||||
"0"
|
|
||||||
}
|
|
||||||
is TwistMlExpr.Binary -> {
|
|
||||||
val left = emit(expression.left, environment)
|
|
||||||
val right = emit(expression.right, environment)
|
|
||||||
when (expression.operator) {
|
|
||||||
"+" -> instruction("add i256 $left, $right")
|
|
||||||
"-" -> instruction("sub i256 $left, $right")
|
|
||||||
"*" -> instruction("mul i256 $left, $right")
|
|
||||||
"/" -> instruction("udiv i256 $left, $right")
|
|
||||||
"%" -> instruction("urem i256 $left, $right")
|
|
||||||
"&&" -> instruction("and i256 $left, $right")
|
|
||||||
"||" -> instruction("or i256 $left, $right")
|
|
||||||
"=", "==", "<", ">" -> {
|
|
||||||
val predicate = mapOf("=" to "eq", "==" to "eq", "<" to "ult", ">" to "ugt").getValue(expression.operator)
|
|
||||||
val comparison = instruction("icmp $predicate i256 $left, $right")
|
|
||||||
instruction("zext i1 $comparison to i256")
|
|
||||||
}
|
|
||||||
else -> error("Unsupported operator ${expression.operator}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
is TwistMlExpr.If -> {
|
|
||||||
val condition = emit(expression.condition, environment)
|
|
||||||
val test = instruction("icmp ne i256 $condition, 0")
|
|
||||||
val id = label++
|
|
||||||
val thenLabel = "then$id"
|
|
||||||
val elseLabel = "else$id"
|
|
||||||
val mergeLabel = "merge$id"
|
|
||||||
lines += " br i1 $test, label %$thenLabel, label %$elseLabel"
|
|
||||||
lines += "$thenLabel:"
|
|
||||||
val thenValue = emit(expression.thenBranch, environment)
|
|
||||||
lines += " br label %$mergeLabel"
|
|
||||||
lines += "$elseLabel:"
|
|
||||||
val elseValue = emit(expression.elseBranch, environment)
|
|
||||||
lines += " br label %$mergeLabel"
|
|
||||||
lines += "$mergeLabel:"
|
|
||||||
instruction("phi i256 [$thenValue, %$thenLabel], [$elseValue, %$elseLabel]")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun call(body: String): String = instruction("call $body")
|
|
||||||
private fun instruction(body: String): String = "%v${value++}".also { lines += " $it = $body" }
|
|
||||||
}
|
|
||||||
|
|
||||||
data class TwistMlContract(val name: String, val functions: List<TwistMlFunction>)
|
|
||||||
|
|
||||||
data class TwistMlFunction(val name: String, val parameters: List<String>, val body: TwistMlExpr)
|
|
||||||
|
|
||||||
sealed interface TwistMlExpr {
|
|
||||||
data class Number(val value: BigInteger) : TwistMlExpr
|
|
||||||
data class Variable(val name: String) : TwistMlExpr
|
|
||||||
data class Let(val name: String, val value: TwistMlExpr, val body: TwistMlExpr) : TwistMlExpr
|
|
||||||
data class If(val condition: TwistMlExpr, val thenBranch: TwistMlExpr, val elseBranch: TwistMlExpr) : TwistMlExpr
|
|
||||||
data class Binary(val operator: String, val left: TwistMlExpr, val right: TwistMlExpr) : TwistMlExpr
|
|
||||||
data class Load(val key: TwistMlExpr) : TwistMlExpr
|
|
||||||
data class Store(val key: TwistMlExpr, val value: TwistMlExpr) : TwistMlExpr
|
|
||||||
data object Caller : TwistMlExpr
|
|
||||||
data object CallValue : TwistMlExpr
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Lowerer(parameters: List<String>) {
|
|
||||||
private val parameterValues = parameters.mapIndexed { index, name ->
|
|
||||||
name to listOf(Instruction(OpCode.PUSH32, Word256.fromLong(((index + 1) * 32).toLong()).toBytes()), Instruction(OpCode.CALLDATA_LOAD))
|
|
||||||
}.toMap()
|
|
||||||
|
|
||||||
fun lower(expression: TwistMlExpr): List<Instruction> = expression.lower(parameterValues) + Instruction(OpCode.RETURN)
|
|
||||||
|
|
||||||
private fun TwistMlExpr.lower(environment: Map<String, List<Instruction>>): List<Instruction> = when (this) {
|
|
||||||
is TwistMlExpr.Number -> listOf(Instruction(OpCode.PUSH32, Word256.of(value).toBytes()))
|
|
||||||
is TwistMlExpr.Variable -> environment[name]?.toList() ?: error("Unknown variable $name")
|
|
||||||
is TwistMlExpr.Let -> body.lower(environment + (name to value.lower(environment)))
|
|
||||||
is TwistMlExpr.Binary -> left.lower(environment) + right.lower(environment) + Instruction(operator.opcode())
|
|
||||||
is TwistMlExpr.Load -> key.lower(environment) + Instruction(OpCode.SLOAD)
|
|
||||||
is TwistMlExpr.Store -> key.lower(environment) + value.lower(environment) + Instruction(OpCode.SSTORE) + Instruction(OpCode.PUSH32, Word256.ZERO.toBytes())
|
|
||||||
TwistMlExpr.Caller -> listOf(Instruction(OpCode.CALLER))
|
|
||||||
TwistMlExpr.CallValue -> listOf(Instruction(OpCode.CALLVALUE))
|
|
||||||
is TwistMlExpr.If -> {
|
|
||||||
val conditionCode = condition.lower(environment)
|
|
||||||
val elseCode = elseBranch.lower(environment)
|
|
||||||
val thenCode = thenBranch.lower(environment)
|
|
||||||
val thenStart = conditionCode.size + elseCode.size + 4
|
|
||||||
val end = thenStart + thenCode.size
|
|
||||||
conditionCode +
|
|
||||||
Instruction(OpCode.PUSH32, Word256.fromLong(thenStart.toLong()).toBytes()) +
|
|
||||||
Instruction(OpCode.JUMPI) +
|
|
||||||
elseCode +
|
|
||||||
Instruction(OpCode.PUSH32, Word256.fromLong(end.toLong()).toBytes()) +
|
|
||||||
Instruction(OpCode.JUMP) +
|
|
||||||
thenCode
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun String.opcode(): OpCode = when (this) {
|
|
||||||
"+" -> OpCode.ADD
|
|
||||||
"-" -> OpCode.SUB
|
|
||||||
"*" -> OpCode.MUL
|
|
||||||
"/" -> OpCode.DIV
|
|
||||||
"%" -> OpCode.MOD
|
|
||||||
"=", "==" -> OpCode.EQ
|
|
||||||
"<" -> OpCode.LT
|
|
||||||
">" -> OpCode.GT
|
|
||||||
"&&" -> OpCode.AND
|
|
||||||
"||" -> OpCode.OR
|
|
||||||
else -> error("Unsupported operator $this")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Parser(source: String) {
|
|
||||||
private val tokens = Regex("[A-Za-z_][A-Za-z0-9_]*|[0-9]+|&&|\\|\\||==|[{}(),;=+*/%<>-]")
|
|
||||||
.findAll(source).map { it.value }.toList()
|
|
||||||
private var position = 0
|
|
||||||
|
|
||||||
fun contract(): TwistMlContract {
|
|
||||||
expect("contract")
|
|
||||||
val name = identifier()
|
|
||||||
expect("{")
|
|
||||||
val functions = buildList {
|
|
||||||
while (peek() != "}") add(function())
|
|
||||||
}
|
|
||||||
expect("}")
|
|
||||||
require(position == tokens.size) { "Unexpected token ${peek()}" }
|
|
||||||
return TwistMlContract(name, functions)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun function(): TwistMlFunction {
|
|
||||||
expect("fun")
|
|
||||||
val name = identifier()
|
|
||||||
expect("(")
|
|
||||||
val parameters = mutableListOf<String>()
|
|
||||||
if (peek() != ")") do { parameters += identifier() } while (takeIf(","))
|
|
||||||
expect(")")
|
|
||||||
expect("=")
|
|
||||||
val body = expression()
|
|
||||||
takeIf(";")
|
|
||||||
return TwistMlFunction(name, parameters, body)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun expression(): TwistMlExpr {
|
|
||||||
if (takeIf("let")) {
|
|
||||||
val name = identifier()
|
|
||||||
expect("=")
|
|
||||||
val value = expression()
|
|
||||||
expect("in")
|
|
||||||
return TwistMlExpr.Let(name, value, expression())
|
|
||||||
}
|
|
||||||
if (takeIf("if")) {
|
|
||||||
val condition = expression()
|
|
||||||
expect("then")
|
|
||||||
val yes = expression()
|
|
||||||
expect("else")
|
|
||||||
return TwistMlExpr.If(condition, yes, expression())
|
|
||||||
}
|
|
||||||
return binary(0)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun binary(minimum: Int): TwistMlExpr {
|
|
||||||
var left = primary()
|
|
||||||
while (precedence(peek()) >= minimum) {
|
|
||||||
val operator = next()
|
|
||||||
val precedence = precedence(operator)
|
|
||||||
left = TwistMlExpr.Binary(operator, left, binary(precedence + 1))
|
|
||||||
}
|
|
||||||
return left
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun primary(): TwistMlExpr = when (val token = next()) {
|
|
||||||
"(" -> expression().also { expect(")") }
|
|
||||||
"load" -> callOne(TwistMlExpr::Load)
|
|
||||||
"store" -> {
|
|
||||||
expect("(")
|
|
||||||
val key = expression()
|
|
||||||
expect(",")
|
|
||||||
TwistMlExpr.Store(key, expression()).also { expect(")") }
|
|
||||||
}
|
|
||||||
"caller" -> TwistMlExpr.Caller
|
|
||||||
"callvalue" -> TwistMlExpr.CallValue
|
|
||||||
else -> token.toBigIntegerOrNull()?.let(TwistMlExpr::Number) ?: TwistMlExpr.Variable(token)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun callOne(build: (TwistMlExpr) -> TwistMlExpr): TwistMlExpr {
|
|
||||||
expect("(")
|
|
||||||
return build(expression()).also { expect(")") }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun precedence(token: String?): Int = when (token) {
|
|
||||||
"||" -> 1
|
|
||||||
"&&" -> 2
|
|
||||||
"=", "==", "<", ">" -> 3
|
|
||||||
"+", "-" -> 4
|
|
||||||
"*", "/", "%" -> 5
|
|
||||||
else -> -1
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun identifier(): String = next().also { require(it.matches(Regex("[A-Za-z_][A-Za-z0-9_]*"))) }
|
|
||||||
private fun peek(): String? = tokens.getOrNull(position)
|
|
||||||
private fun next(): String = peek()?.also { position++ } ?: error("Unexpected end of source")
|
|
||||||
private fun expect(value: String) = require(next() == value) { "Expected $value" }
|
|
||||||
private fun takeIf(value: String): Boolean = (peek() == value).also { if (it) position++ }
|
|
||||||
}
|
|
||||||
|
|
@ -24,13 +24,5 @@ class PersistentToken(override val symbol: String, private val store: KeyValueSt
|
||||||
store.put(key(to), (balanceOf(to).value + amount.value).toByteArray())
|
store.put(key(to), (balanceOf(to).value + amount.value).toByteArray())
|
||||||
}
|
}
|
||||||
|
|
||||||
fun burn(from: Address, amount: Amount): Boolean =
|
|
||||||
synchronized(this) {
|
|
||||||
val balance = balanceOf(from).value
|
|
||||||
if (amount.value.signum() <= 0 || balance < amount.value) return false
|
|
||||||
store.put(key(from), (balance - amount.value).toByteArray())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun key(owner: Address) = "$symbol:${owner.value}".encodeToByteArray()
|
private fun key(owner: Address) = "$symbol:${owner.value}".encodeToByteArray()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,8 +18,6 @@ data class TwistoMetadata(
|
||||||
val name: String = "Twisto",
|
val name: String = "Twisto",
|
||||||
val symbol: String = "TWISTO",
|
val symbol: String = "TWISTO",
|
||||||
val description: String,
|
val description: String,
|
||||||
val decimals: Int = 18,
|
|
||||||
val maxSupply: String? = null,
|
|
||||||
val image: String? = null,
|
val image: String? = null,
|
||||||
val externalUrl: String? = null,
|
val externalUrl: String? = null,
|
||||||
val issuerDid: String? = null,
|
val issuerDid: String? = null,
|
||||||
|
|
@ -30,8 +28,6 @@ data class TwistoMetadata(
|
||||||
require(name.isNotBlank() && name.length <= 128)
|
require(name.isNotBlank() && name.length <= 128)
|
||||||
require(symbol.matches(Regex("[A-Z0-9]{2,12}")))
|
require(symbol.matches(Regex("[A-Z0-9]{2,12}")))
|
||||||
require(description.length <= 4_096)
|
require(description.length <= 4_096)
|
||||||
require(decimals in 0..36)
|
|
||||||
require(maxSupply == null || (maxSupply.toBigIntegerOrNull()?.signum() ?: -1) >= 0)
|
|
||||||
require(
|
require(
|
||||||
attributes.size <= 64 &&
|
attributes.size <= 64 &&
|
||||||
attributes.all { it.key.length <= 64 && it.value.length <= 512 }
|
attributes.all { it.key.length <= 64 && it.value.length <= 512 }
|
||||||
|
|
@ -48,10 +44,6 @@ data class TwistoMetadata(
|
||||||
append(symbol.escape())
|
append(symbol.escape())
|
||||||
append("\",\"description\":\"")
|
append("\",\"description\":\"")
|
||||||
append(description.escape())
|
append(description.escape())
|
||||||
append("\",\"decimals\":")
|
|
||||||
append(decimals)
|
|
||||||
append(",\"maxSupply\":")
|
|
||||||
appendJson(maxSupply)
|
|
||||||
append("\",\"image\":")
|
append("\",\"image\":")
|
||||||
appendJson(image)
|
appendJson(image)
|
||||||
append(",\"externalUrl\":")
|
append(",\"externalUrl\":")
|
||||||
|
|
@ -90,14 +82,9 @@ class TwistoToken(
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
check(state.get(DEPLOYED) == null) { "Twisto is already deployed" }
|
check(state.get(DEPLOYED) == null) { "Twisto is already deployed" }
|
||||||
require(metadata.symbol == SYMBOL)
|
require(metadata.symbol == SYMBOL)
|
||||||
val cap = metadata.maxSupply?.let(::BigInteger)
|
|
||||||
require(cap == null || initialSupply.value <= cap) { "Initial supply exceeds cap" }
|
|
||||||
val pointer = content.put(metadata.encode())
|
val pointer = content.put(metadata.encode())
|
||||||
state.put(METADATA, pointer.value.encodeToByteArray())
|
state.put(METADATA, pointer.value.encodeToByteArray())
|
||||||
state.put(SUPPLY, initialSupply.value.toByteArray())
|
state.put(SUPPLY, initialSupply.value.toByteArray())
|
||||||
cap?.let { state.put(CAP, it.toByteArray()) }
|
|
||||||
state.put(PAUSED, byteArrayOf(0))
|
|
||||||
state.put(OWNER, owner.value.encodeToByteArray())
|
|
||||||
state.put(DEPLOYED, byteArrayOf(1))
|
state.put(DEPLOYED, byteArrayOf(1))
|
||||||
if (initialSupply.value.signum() > 0) token.mint(owner, initialSupply)
|
if (initialSupply.value.signum() > 0) token.mint(owner, initialSupply)
|
||||||
pointer
|
pointer
|
||||||
|
|
@ -109,19 +96,11 @@ class TwistoToken(
|
||||||
|
|
||||||
fun totalSupply(): Amount = Amount(state.get(SUPPLY)?.let(::BigInteger) ?: BigInteger.ZERO)
|
fun totalSupply(): Amount = Amount(state.get(SUPPLY)?.let(::BigInteger) ?: BigInteger.ZERO)
|
||||||
|
|
||||||
fun cap(): Amount? = state.get(CAP)?.let { Amount(BigInteger(it)) }
|
|
||||||
|
|
||||||
fun paused(): Boolean = state.get(PAUSED)?.firstOrNull() == 1.toByte()
|
|
||||||
|
|
||||||
fun mint(caller: Address, recipient: Address, amount: Amount) =
|
fun mint(caller: Address, recipient: Address, amount: Amount) =
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
require(caller == owner) { "Only the owner may mint" }
|
require(caller == owner) { "Only the owner may mint" }
|
||||||
check(state.get(DEPLOYED) != null) { "Twisto is not deployed" }
|
check(state.get(DEPLOYED) != null) { "Twisto is not deployed" }
|
||||||
check(!paused()) { "Twisto transfers are paused" }
|
|
||||||
require(amount.value.signum() > 0)
|
require(amount.value.signum() > 0)
|
||||||
require(cap()?.value?.let { totalSupply().value + amount.value <= it } ?: true) {
|
|
||||||
"Mint exceeds cap"
|
|
||||||
}
|
|
||||||
token.mint(recipient, amount)
|
token.mint(recipient, amount)
|
||||||
state.put(SUPPLY, (totalSupply().value + amount.value).toByteArray())
|
state.put(SUPPLY, (totalSupply().value + amount.value).toByteArray())
|
||||||
}
|
}
|
||||||
|
|
@ -129,50 +108,7 @@ class TwistoToken(
|
||||||
override fun balanceOf(owner: Address): Amount = token.balanceOf(owner)
|
override fun balanceOf(owner: Address): Amount = token.balanceOf(owner)
|
||||||
|
|
||||||
override fun transfer(from: Address, to: Address, amount: Amount): Boolean =
|
override fun transfer(from: Address, to: Address, amount: Amount): Boolean =
|
||||||
synchronized(this) {
|
token.transfer(from, to, amount)
|
||||||
check(state.get(DEPLOYED) != null) { "Twisto is not deployed" }
|
|
||||||
check(!paused()) { "Twisto transfers are paused" }
|
|
||||||
token.transfer(from, to, amount)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun burn(caller: Address, amount: Amount): Boolean =
|
|
||||||
synchronized(this) {
|
|
||||||
check(!paused()) { "Twisto transfers are paused" }
|
|
||||||
if (!token.burn(caller, amount)) return false
|
|
||||||
state.put(SUPPLY, (totalSupply().value - amount.value).toByteArray())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun approve(owner: Address, spender: Address, amount: Amount) =
|
|
||||||
synchronized(this) {
|
|
||||||
check(!paused()) { "Twisto transfers are paused" }
|
|
||||||
require(amount.value.signum() >= 0)
|
|
||||||
state.put(allowanceKey(owner, spender), amount.value.toByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun allowance(owner: Address, spender: Address): Amount =
|
|
||||||
Amount(state.get(allowanceKey(owner, spender))?.let(::BigInteger) ?: BigInteger.ZERO)
|
|
||||||
|
|
||||||
fun transferFrom(spender: Address, from: Address, to: Address, amount: Amount): Boolean =
|
|
||||||
synchronized(this) {
|
|
||||||
check(!paused()) { "Twisto transfers are paused" }
|
|
||||||
val approved = allowance(from, spender).value
|
|
||||||
if (amount.value.signum() <= 0 || approved < amount.value || !token.transfer(from, to, amount)) return false
|
|
||||||
state.put(allowanceKey(from, spender), (approved - amount.value).toByteArray())
|
|
||||||
true
|
|
||||||
}
|
|
||||||
|
|
||||||
fun pause(caller: Address) = requireOwner(caller) { state.put(PAUSED, byteArrayOf(1)) }
|
|
||||||
|
|
||||||
fun unpause(caller: Address) = requireOwner(caller) { state.put(PAUSED, byteArrayOf(0)) }
|
|
||||||
|
|
||||||
fun transferOwnership(caller: Address, nextOwner: Address) =
|
|
||||||
requireOwner(caller) {
|
|
||||||
require(nextOwner.value.isNotBlank())
|
|
||||||
state.put(OWNER, nextOwner.value.encodeToByteArray())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun owner(): Address = state.get(OWNER)?.decodeToString()?.let(::Address) ?: owner
|
|
||||||
|
|
||||||
fun contractIr(): ContractIr =
|
fun contractIr(): ContractIr =
|
||||||
contract("Twisto") {
|
contract("Twisto") {
|
||||||
|
|
@ -191,22 +127,6 @@ class TwistoToken(
|
||||||
storageLoad()
|
storageLoad()
|
||||||
returnWord()
|
returnWord()
|
||||||
}
|
}
|
||||||
function("approve", 4) {
|
|
||||||
storageStore()
|
|
||||||
returnWord()
|
|
||||||
}
|
|
||||||
function("transferFrom", 5) {
|
|
||||||
storageStore()
|
|
||||||
returnWord()
|
|
||||||
}
|
|
||||||
function("burn", 6) {
|
|
||||||
storageStore()
|
|
||||||
returnWord()
|
|
||||||
}
|
|
||||||
function("pause", 7) {
|
|
||||||
storageStore()
|
|
||||||
returnWord()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private companion object {
|
private companion object {
|
||||||
|
|
@ -214,15 +134,5 @@ class TwistoToken(
|
||||||
val DEPLOYED = "twisto:deployed".encodeToByteArray()
|
val DEPLOYED = "twisto:deployed".encodeToByteArray()
|
||||||
val METADATA = "twisto:metadata".encodeToByteArray()
|
val METADATA = "twisto:metadata".encodeToByteArray()
|
||||||
val SUPPLY = "twisto:supply".encodeToByteArray()
|
val SUPPLY = "twisto:supply".encodeToByteArray()
|
||||||
val CAP = "twisto:cap".encodeToByteArray()
|
|
||||||
val PAUSED = "twisto:paused".encodeToByteArray()
|
|
||||||
val OWNER = "twisto:owner".encodeToByteArray()
|
|
||||||
fun allowanceKey(owner: Address, spender: Address) =
|
|
||||||
"twisto:allowance:${owner.value}:${spender.value}".encodeToByteArray()
|
|
||||||
}
|
|
||||||
|
|
||||||
private inline fun requireOwner(caller: Address, action: () -> Unit) = synchronized(this) {
|
|
||||||
require(caller == owner()) { "Only the owner may perform this action" }
|
|
||||||
action()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,6 @@ import rip.crit.twist.bips.FileStorageContract
|
||||||
import rip.crit.twist.bytecode.TwistBytecode
|
import rip.crit.twist.bytecode.TwistBytecode
|
||||||
import rip.crit.twist.compiler.TwistCompiler
|
import rip.crit.twist.compiler.TwistCompiler
|
||||||
import rip.crit.twist.compiler.LispIrCompiler
|
import rip.crit.twist.compiler.LispIrCompiler
|
||||||
import rip.crit.twist.compiler.TwistMlCompiler
|
|
||||||
import rip.crit.twist.compiler.contract
|
import rip.crit.twist.compiler.contract
|
||||||
import rip.crit.twist.core.Address
|
import rip.crit.twist.core.Address
|
||||||
import rip.crit.twist.core.Amount
|
import rip.crit.twist.core.Amount
|
||||||
|
|
@ -45,8 +44,6 @@ import rip.crit.twist.tvm.Calldata
|
||||||
import rip.crit.twist.tvm.TransientStorage
|
import rip.crit.twist.tvm.TransientStorage
|
||||||
import rip.crit.twist.tvm.Word256
|
import rip.crit.twist.tvm.Word256
|
||||||
import rip.crit.twist.tvm.BytecodeVirtualMachine
|
import rip.crit.twist.tvm.BytecodeVirtualMachine
|
||||||
import rip.crit.twist.tvm.ExecutionContext
|
|
||||||
import rip.crit.twist.tvm.StateExecutionHost
|
|
||||||
import rip.crit.twist.bytecode.Instruction
|
import rip.crit.twist.bytecode.Instruction
|
||||||
import rip.crit.twist.bytecode.OpCode
|
import rip.crit.twist.bytecode.OpCode
|
||||||
import rip.crit.twist.p2p.PeerNetwork
|
import rip.crit.twist.p2p.PeerNetwork
|
||||||
|
|
@ -385,47 +382,6 @@ class AppTest {
|
||||||
assertFailsWith<IllegalArgumentException> { LispIrCompiler().compile(source, "mallory") }
|
assertFailsWith<IllegalArgumentException> { LispIrCompiler().compile(source, "mallory") }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun twistMlLowersFunctionalExpressionsToExecutableBytecode() {
|
|
||||||
val source =
|
|
||||||
"""
|
|
||||||
contract Arithmetic {
|
|
||||||
fun max(left, right) = if left > right then left else right;
|
|
||||||
fun stored(key) = let value = load(key) in value + 1;
|
|
||||||
}
|
|
||||||
""".trimIndent()
|
|
||||||
val contract = TwistMlCompiler().compile(source)
|
|
||||||
assertEquals(listOf("max", "stored"), contract.functions.map { it.name })
|
|
||||||
val input =
|
|
||||||
Word256.fromLong(1).toBytes() +
|
|
||||||
Word256.fromLong(3).toBytes() +
|
|
||||||
Word256.fromLong(7).toBytes()
|
|
||||||
val result = BytecodeVirtualMachine().execute(contract.bytecode(), input, Address("ml"))
|
|
||||||
assertTrue(result.success)
|
|
||||||
assertEquals(BigInteger.valueOf(7), Word256.fromBytes(result.output).toBigInteger())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun twistoTwistMlContractInitializesAndTransfersOnTheVm() {
|
|
||||||
val contract = TwistMlCompiler().compile(Files.readString(Path.of("protocols/tokens/twisto.twml")))
|
|
||||||
val code = contract.bytecode()
|
|
||||||
val state = InMemoryWorldState()
|
|
||||||
val address = Address("twisto")
|
|
||||||
val owner = Address("owner")
|
|
||||||
fun call(caller: Address, selector: Long, vararg arguments: Word256) =
|
|
||||||
BytecodeVirtualMachine().execute(
|
|
||||||
code,
|
|
||||||
Word256.fromLong(selector).toBytes() + arguments.fold(byteArrayOf()) { bytes, word -> bytes + word.toBytes() },
|
|
||||||
StateExecutionHost(ExecutionContext(address, caller, Amount(BigInteger.ZERO)), state),
|
|
||||||
)
|
|
||||||
assertTrue(call(owner, 1, Word256.fromLong(99), Word256.fromLong(10), Word256.fromLong(10)).success)
|
|
||||||
val recipient = Word256.fromBytes(Sha256.bytes("recipient".encodeToByteArray()))
|
|
||||||
assertTrue(call(owner, 6, recipient, Word256.fromLong(4)).success)
|
|
||||||
val ownerKey = Word256.fromBytes(Sha256.bytes(owner.value.encodeToByteArray()))
|
|
||||||
assertEquals(BigInteger.valueOf(6), Word256.fromBytes(call(owner, 5, ownerKey).output).toBigInteger())
|
|
||||||
assertEquals(BigInteger.valueOf(4), Word256.fromBytes(call(owner, 5, recipient).output).toBigInteger())
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun didMethodsResolveDocuments() {
|
fun didMethodsResolveDocuments() {
|
||||||
val publicKey = ByteArray(32) { it.toByte() }
|
val publicKey = ByteArray(32) { it.toByte() }
|
||||||
|
|
@ -504,25 +460,6 @@ class AppTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun twistoSupportsAllowancesBurningPausesAndSupplyCaps() {
|
|
||||||
val owner = Address("owner")
|
|
||||||
val alice = Address("alice")
|
|
||||||
val bob = Address("bob")
|
|
||||||
val token = TwistoToken(owner, InMemoryKeyValueStore(), FileStorageContract(Files.createTempDirectory("twisto-token")))
|
|
||||||
token.deploy(TwistoMetadata(description = "Capped", maxSupply = "20"), Amount(BigInteger.TEN))
|
|
||||||
token.approve(owner, alice, Amount(BigInteger.valueOf(4)))
|
|
||||||
assertTrue(token.transferFrom(alice, owner, bob, Amount(BigInteger.valueOf(4))))
|
|
||||||
assertEquals(BigInteger.ZERO, token.allowance(owner, alice).value)
|
|
||||||
assertTrue(token.burn(bob, Amount(BigInteger.ONE)))
|
|
||||||
assertEquals(BigInteger.valueOf(9), token.totalSupply().value)
|
|
||||||
token.pause(owner)
|
|
||||||
assertFailsWith<IllegalStateException> { token.transfer(owner, bob, Amount(BigInteger.ONE)) }
|
|
||||||
token.unpause(owner)
|
|
||||||
token.mint(owner, owner, Amount(BigInteger.valueOf(11)))
|
|
||||||
assertFailsWith<IllegalArgumentException> { token.mint(owner, owner, Amount(BigInteger.ONE)) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hex(value: String): ByteArray = value.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
private fun hex(value: String): ByteArray = value.chunked(2).map { it.toInt(16).toByte() }.toByteArray()
|
||||||
private fun ByteArray.hex(): String = joinToString("") { "%02x".format(it) }
|
private fun ByteArray.hex(): String = joinToString("") { "%02x".format(it) }
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,15 @@ import java.nio.file.Files
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import rip.crit.twist.compiler.TwistCompiler
|
import rip.crit.twist.compiler.TwistCompiler
|
||||||
import rip.crit.twist.compiler.LispIrCompiler
|
import rip.crit.twist.compiler.LispIrCompiler
|
||||||
import rip.crit.twist.compiler.TwistMlCompiler
|
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
require(args.size in 2..3) {
|
require(args.size in 2..3) {
|
||||||
"Usage is twistc <source.twistasm|source.twistl|source.twml> <output.tbc> [principal]"
|
"Usage is twistc <source.twistasm|source.twistl> <output.tbc> [principal]"
|
||||||
}
|
}
|
||||||
val source = Path.of(args[0])
|
val source = Path.of(args[0])
|
||||||
val text = Files.readString(source)
|
val text = Files.readString(source)
|
||||||
val bytecode =
|
val bytecode =
|
||||||
if (source.fileName.toString().endsWith(".twml")) {
|
if (source.fileName.toString().endsWith(".twistl")) {
|
||||||
TwistMlCompiler().compile(text).bytecode()
|
|
||||||
} else if (source.fileName.toString().endsWith(".twistl")) {
|
|
||||||
LispIrCompiler().compile(text, args.getOrNull(2) ?: "anonymous")
|
LispIrCompiler().compile(text, args.getOrNull(2) ?: "anonymous")
|
||||||
} else {
|
} else {
|
||||||
TwistCompiler().compile(text)
|
TwistCompiler().compile(text)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,31 @@
|
||||||
package rip.crit.twist.tool.twisto
|
package rip.crit.twist.tool.twisto
|
||||||
|
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.nio.file.Files
|
|
||||||
import java.nio.file.Path
|
import java.nio.file.Path
|
||||||
import rip.crit.twist.bips.FileStorageContract
|
import rip.crit.twist.bips.FileStorageContract
|
||||||
import rip.crit.twist.compiler.TwistMlCompiler
|
import rip.crit.twist.core.Address
|
||||||
|
import rip.crit.twist.core.Amount
|
||||||
|
import rip.crit.twist.store.FileKeyValueStore
|
||||||
import rip.crit.twist.twisto.TwistoMetadata
|
import rip.crit.twist.twisto.TwistoMetadata
|
||||||
import rip.crit.twist.tvm.Word256
|
import rip.crit.twist.twisto.TwistoToken
|
||||||
|
|
||||||
fun main(args: Array<String>) {
|
fun main(args: Array<String>) {
|
||||||
require(args.size in 1..3) {
|
require(args.size in 1..3) {
|
||||||
"Usage is twisto <data-folder> [owner-label] [initial-supply]"
|
"Usage is twisto <data-folder> [owner] [initial-supply]"
|
||||||
}
|
}
|
||||||
val root = Path.of(args[0])
|
val root = Path.of(args[0])
|
||||||
val owner = args.getOrNull(1) ?: "twisto-owner"
|
val owner = Address(args.getOrNull(1) ?: "twisto-owner")
|
||||||
val supply = BigInteger(args.getOrNull(2) ?: "1000000")
|
val token =
|
||||||
val metadata = FileStorageContract(root.resolve("metadata"))
|
TwistoToken(
|
||||||
val pointer = metadata.put(TwistoMetadata(description = "Named token on the Twist network").encode())
|
owner,
|
||||||
val source = checkNotNull(object {}.javaClass.getResource("/twisto.twml")) { "Missing Twisto contract source" }.readText()
|
FileKeyValueStore(root.resolve("state")),
|
||||||
val code = TwistMlCompiler().compile(source).bytecode()
|
FileStorageContract(root.resolve("metadata")),
|
||||||
Files.createDirectories(root)
|
)
|
||||||
Files.write(root.resolve("twisto.tbc"), code)
|
val pointer =
|
||||||
val initialization =
|
token.metadataPointer()
|
||||||
Word256.fromLong(1).toBytes() +
|
?: token.deploy(
|
||||||
Word256.fromBytes(pointer.value.chunked(2).map { it.toInt(16).toByte() }.toByteArray()).toBytes() +
|
TwistoMetadata(description = "Named token on the Twist network"),
|
||||||
Word256.of(supply).toBytes() +
|
Amount(BigInteger(args.getOrNull(2) ?: "1000000")),
|
||||||
Word256.of(supply).toBytes()
|
)
|
||||||
Files.write(root.resolve("initialize.calldata"), initialization)
|
println("deployed symbol=${token.symbol} metadata=${pointer.value} owner=${owner.value}")
|
||||||
println("compiled contract=${root.resolve("twisto.tbc")} metadata=${pointer.value} owner-label=$owner")
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +0,0 @@
|
||||||
contract Twisto {
|
|
||||||
fun initialize(metadata, supply, cap) = if load(0) == 0 then let ignored = store(0, caller) in let ignored = store(1, metadata) in let ignored = store(2, cap) in let ignored = store(3, supply) in store(caller, supply) else 0;
|
|
||||||
fun metadata() = load(1);
|
|
||||||
fun total_supply() = load(3);
|
|
||||||
fun cap() = load(2);
|
|
||||||
fun balance_of(account) = load(account);
|
|
||||||
fun transfer(to, amount) = let from = caller in let balance = load(from) in if amount > balance then 0 else let ignored = store(from, balance - amount) in store(to, load(to) + amount);
|
|
||||||
fun mint(to, amount) = let owner = load(0) in let supply = load(3) in let ceiling = load(2) in if caller == owner then if ceiling < supply + amount then 0 else let ignored = store(3, supply + amount) in store(to, load(to) + amount) else 0;
|
|
||||||
fun burn(amount) = let account = caller in let balance = load(account) in let supply = load(3) in if amount > balance then 0 else let ignored = store(account, balance - amount) in store(3, supply - amount);
|
|
||||||
}
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
contract Twisto {
|
|
||||||
fun initialize(metadata, supply, cap) =
|
|
||||||
if load(0) == 0 then
|
|
||||||
let ignored = store(0, caller) in
|
|
||||||
let ignored = store(1, metadata) in
|
|
||||||
let ignored = store(2, cap) in
|
|
||||||
let ignored = store(3, supply) in
|
|
||||||
store(caller, supply)
|
|
||||||
else 0;
|
|
||||||
|
|
||||||
fun metadata() = load(1);
|
|
||||||
fun total_supply() = load(3);
|
|
||||||
fun cap() = load(2);
|
|
||||||
fun balance_of(account) = load(account);
|
|
||||||
|
|
||||||
fun transfer(to, amount) =
|
|
||||||
let from = caller in
|
|
||||||
let balance = load(from) in
|
|
||||||
if amount > balance then 0 else
|
|
||||||
let ignored = store(from, balance - amount) in
|
|
||||||
store(to, load(to) + amount);
|
|
||||||
|
|
||||||
fun mint(to, amount) =
|
|
||||||
let owner = load(0) in
|
|
||||||
let supply = load(3) in
|
|
||||||
let ceiling = load(2) in
|
|
||||||
if caller == owner then
|
|
||||||
if ceiling < supply + amount then 0 else
|
|
||||||
let ignored = store(3, supply + amount) in
|
|
||||||
store(to, load(to) + amount)
|
|
||||||
else 0;
|
|
||||||
|
|
||||||
fun burn(amount) =
|
|
||||||
let account = caller in
|
|
||||||
let balance = load(account) in
|
|
||||||
let supply = load(3) in
|
|
||||||
if amount > balance then 0 else
|
|
||||||
let ignored = store(account, balance - amount) in
|
|
||||||
store(3, supply - amount);
|
|
||||||
}
|
|
||||||
Loading…
Reference in a new issue