All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.neow3j.contract.ScriptBuilder Maven / Gradle / Ivy

There is a newer version: 3.6.1
Show newest version
package io.neow3j.contract;

import static java.nio.charset.StandardCharsets.UTF_8;

import io.neow3j.constants.InteropServiceCode;
import io.neow3j.constants.OpCode;
import io.neow3j.utils.BigIntegers;
import io.neow3j.utils.Numeric;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.List;

public class ScriptBuilder {

    private DataOutputStream stream;
    private ByteBuffer buffer;
    private ByteArrayOutputStream byteStream;

    public ScriptBuilder() {
        byteStream = new ByteArrayOutputStream();
        stream = new DataOutputStream(byteStream);
        buffer = ByteBuffer.wrap(new byte[8]).order(ByteOrder.LITTLE_ENDIAN);
    }

    /**
     * Appends an OpCode to the script.
     *
     * @param opCode The OpCode to append.
     * @return this ScriptBuilder object.
     */
    public ScriptBuilder opCode(OpCode opCode) {
        writeByte(opCode.getCode());
        return this;
    }

    /**
     * Appends an OpCode and a belonging argument to the script.
     *
     * @param opCode   The OpCode to append.
     * @param argument The argument of the OpCode.
     * @return this ScriptBuilder object.
     */
    public ScriptBuilder opCode(OpCode opCode, byte[] argument) {
        writeByte(opCode.getCode());
        write(argument);
        return this;
    }

    /**
     * Appends a call to the contract denoted by the given script hash.
     *
     * @param scriptHash The script hash of the contract to call.
     * @param method     The method to call.
     * @param params     The parameters that will be used in the call. Need to be in correct order.
     * @return this ScriptBuilder object.
     */
    public ScriptBuilder contractCall(ScriptHash scriptHash, String method,
            List params) {

        pushParams(params);
        if (method != null) {
            pushData(method);
        }
        pushData(scriptHash.toArray());
        sysCall(InteropServiceCode.SYSTEM_CONTRACT_CALL);
        return this;
    }

    public ScriptBuilder sysCall(InteropServiceCode operation) {
        writeByte(OpCode.SYSCALL.getCode());
        write(Numeric.hexStringToByteArray(operation.getHash()));
        return this;
    }

    /**
     * Adds the given contract parameters to the script.
     * 

* Example with two parameters in the list: *

     *  PUSHBYTES4 a3b00183
     *  PUSHBYTES20 0100000000000000000000000000000000000000
     *  PUSH2
     *  PACK
     * 
*

* This method should also be used if the parameters list is empty. In that case the script * looks like the following: *

     *  PUSH0
     *  PACK
     * 
* * @param params The list of parameters to add. * @return this */ public ScriptBuilder pushParams(List params) { // Push params in reverse order. for (int i = params.size() - 1; i >= 0; i--) { pushParam(params.get(i)); } // Even if the parameter list is empty we need to push PUSH0 and PACK OpCodes. pushInteger(params.size()); opCode(OpCode.PACK); return this; } public ScriptBuilder pushParam(ContractParameter param) { Object value = param.getValue(); switch (param.getParamType()) { case BYTE_ARRAY: case SIGNATURE: case PUBLIC_KEY: pushData((byte[]) value); break; case BOOLEAN: pushBoolean((boolean) value); break; case INTEGER: pushInteger((BigInteger) value); break; case HASH160: case HASH256: pushData(((ScriptHash) value).toArray()); break; case STRING: pushData((String) value); break; case ARRAY: pushArray((ContractParameter[]) value); break; default: throw new IllegalArgumentException("Parameter type '" + param.getParamType() + "' not supported."); } return this; } /** * Adds a push operation with the given integer to the script. * * @param v The number to push. * @return this. * @throws IllegalArgumentException if the given number is smaller than -1. */ public ScriptBuilder pushInteger(int v) { return pushInteger(BigInteger.valueOf(v)); } private static final BigInteger minusOne = BigInteger.valueOf(-1); private static final BigInteger sixteen = BigInteger.valueOf(16); /** * Adds a push operation with the given integer to the script. The integer is encoded in its * two's complement and in little-endian order. *

* The integer can be up to 32 bytes long. * * @param v The integer to push. * @return this. * @throws IllegalArgumentException if the given integer is smaller than -1 or takes more space * than 32 bytes. */ public ScriptBuilder pushInteger(BigInteger v) { int i = v.intValue(); if (v.compareTo(minusOne) >= 0 && v.compareTo(sixteen) <= 0) { int opcode = OpCode.PUSH0.getCode() + i; return this.opCode(OpCode.get(opcode)); } byte[] bytes = BigIntegers.toLittleEndianByteArray(v); if (bytes.length == 1) { return this.opCode(OpCode.PUSHINT8, bytes); } if (bytes.length == 2) { return this.opCode(OpCode.PUSHINT16, bytes); } if (bytes.length <= 4) { return this.opCode(OpCode.PUSHINT32, padRight(bytes, 4)); } if (bytes.length <= 8) { return this.opCode(OpCode.PUSHINT64, padRight(bytes, 8)); } if (bytes.length <= 16) { return this.opCode(OpCode.PUSHINT128, padRight(bytes, 16)); } if (bytes.length <= 32) { return this.opCode(OpCode.PUSHINT256, padRight(bytes, 32)); } throw new IllegalArgumentException("The given number (" + v.toString() + ") is out of " + "range."); } private byte[] padRight(byte[] data, int desiredLenght) { if (data.length >= desiredLenght) { return data; } byte[] paddedData = new byte[desiredLenght]; System.arraycopy(data, 0, paddedData, 0, data.length); return paddedData; } public ScriptBuilder pushBoolean(boolean bool) { if (bool) { writeByte(OpCode.PUSH1.getCode()); } else { writeByte(OpCode.PUSH0.getCode()); } return this; } /** * Adds the data to the script, prefixed with the correct code for its length. * * @param data The data to add to the script. * @return this ScriptBuilder object. */ public ScriptBuilder pushData(String data) { if (data != null) { pushData(data.getBytes(UTF_8)); } else { pushData("".getBytes()); } return this; } /** * Adds the data to the script, prefixed with the correct code for its length. * * @param data The data to add to the script. * @return this ScriptBuilder object. */ public ScriptBuilder pushData(byte[] data) { if (data == null) { throw new IllegalArgumentException("Data must not be null."); } if (data.length < 256) { this.opCode(OpCode.PUSHDATA1); this.writeByte((byte) data.length); this.write(data); } else if (data.length < 65536) { this.opCode(OpCode.PUSHDATA2); this.writeShort(data.length); this.write(data); } else { this.opCode(OpCode.PUSHDATA4); this.writeInt(data.length); this.write(data); } return this; } public ScriptBuilder pushArray(ContractParameter[] params) { for (int i = params.length - 1; i >= 0; i--) { pushParam(params[i]); } pushInteger(params.length); opCode(OpCode.PACK); return this; } public ScriptBuilder pack() { opCode(OpCode.PACK); return this; } private void writeByte(int v) { try { stream.writeByte(v); } catch (IOException e) { throw new IllegalStateException("Got IOException without doing IO."); } } private void writeShort(int v) { buffer.putInt(0, v); try { stream.write(buffer.array(), 0, 2); } catch (IOException e) { throw new IllegalStateException("Got IOException without doing IO."); } } private void writeInt(int v) { buffer.putInt(0, v); try { stream.write(buffer.array(), 0, 4); } catch (IOException e) { throw new IllegalStateException("Got IOException without doing IO."); } } private void write(byte[] data) { try { stream.write(data); } catch (IOException e) { throw new IllegalStateException("Got IOException without doing IO."); } } public byte[] toArray() { try { stream.flush(); } catch (IOException e) { throw new IllegalStateException("Got IOException without doing IO."); } return byteStream.toByteArray(); } /** * Builds a verification script for the given public key. * * @param encodedPublicKey The public key encoded in compressed format. * @return the script. */ public static byte[] buildVerificationScript(byte[] encodedPublicKey) { return new ScriptBuilder() .pushData(encodedPublicKey) .opCode(OpCode.PUSHNULL) .sysCall(InteropServiceCode.NEO_CRYPTO_VERIFYWITHECDSASECP256R1) .toArray(); } /** * Builds a verification script for a multi signature account from the given public keys. * * @param encodedPublicKeys The public keys encoded in compressed format. * @param signingThreshold The desired minimum number of signatures required when using the * multi-sig account. * @return the script. */ public static byte[] buildVerificationScript(List encodedPublicKeys, int signingThreshold) { ScriptBuilder builder = new ScriptBuilder().pushInteger(signingThreshold); encodedPublicKeys.forEach(builder::pushData); return builder .pushInteger(encodedPublicKeys.size()) .opCode(OpCode.PUSHNULL) .sysCall(InteropServiceCode.NEO_CRYPTO_CHECKMULTISIGWITHECDSASECP256R1) .toArray(); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy