3

I have a problem with adding new opcode to solidity. I'm using solc (on C++) and geth(ethereum on Go). I want to add new opcode, that takes address payable, uint256, uint256, bytes memory and returns bytes memory. So I have a problem with return value.

Some peaces of code below, I will skip some files, to make question shorter.

  1. Solc
  • libsolidity/codegen/ExpressionCompiler.cpp
// ExpressionCompiler::visit(FunctionCall const& _functionCall)
case FunctionType::Kind::MyOpcode:
{
    acceptAndConvert(*arguments[0], *function.parameterTypes()[0], true);
    acceptAndConvert(*arguments[1], *function.parameterTypes()[1], true);
    acceptAndConvert(*arguments[2], *function.parameterTypes()[2], true);
    arguments[3]->accept(*this);
    utils().fetchFreeMemoryPointer();
    utils().packedEncode(
        {arguments[3]->annotation().type},
        {TypeProvider::array(DataLocation::Memory, true)}
    );
    utils().toSizeAfterFreeMemoryPointer();

    m_context << Instruction::MYOPCODE;
}
  • libsolidity/analysis/GlobalContext.cpp
// inline vector<shared_ptr<MagicVariableDeclaration const>> constructMagicVariables()
magicVarDecl("myopcode", TypeProvider::function(strings{"address payable", "uint256", "uint256", "bytes memory"}, strings{"bytes memory"}, FunctionType::Kind::MyOpcode, false, StateMutability::Payable)),
  • libevmasm/Instruction.cpp
// static std::map<Instruction, InstructionInfo> const c_instructionInfo =
{ Instruction::MYOPCODE,       { "MYOPCODE",          0, 5, 1, true, Tier::Base } }
  1. Geth
  • core/vm/jump_table.go
// func newFrontierInstructionSet() JumpTable {
CALLACTOR: {
    execute:    opMyOpCode,
    dynamicGas: gasCallActor,
    minStack:   minStack(5, 1),
    maxStack:   maxStack(5, 1),
    memorySize: memoryReturn,
    writes:     true,
    returns:    true,
},
  • core/vm/instructions.go
func opMyOpcode(pc *uint64, interpreter *EVMInterpreter, callContext *callCtx) ([]byte, error) {
    inoffset, insize := callContext.stack.pop(), callContext.stack.pop()
    params := callContext.memory.GetPtr(int64(inoffset.Uint64()), int64(insize.Uint64()))

    secondValue := callContext.stack.pop()

    firstValue := callContext.stack.pop()

    addr := callContext.stack.pop()

    // ... Do smth with input ...

    outoffset := inoffset.Uint64() + insize.Uint64()

    callContext.memory.Set(outoffset, 1, []byte{0x1})
    tmp := make([]byte, 1)
    tmp[0] = 0x98
    callContext.memory.Set(outoffset + 1, 1, tmp)

    callContext.stack.push(uint256.NewInt().SetUint64(outoffset))

    return tmp, nil
}
  1. Smart contract
pragma solidity >=0.6.0; // SPDX-License-Identifier: GPL-3
contract test {
    event ReturnValue(address payable _from, bytes data);
    function f() public returns(bytes memory){
        address payable addr1 = payable(msg.sender);
        bytes memory params = new bytes(2);
        params[0] = 0x21;
        params[1] = 0x22;
        bytes memory result = myopcode(addr1, 0x11, 0x12, params);
        emit ReturnValue(addr1, result);
        return result;
    }
}

When I run that code I get invalid jump destination. So, what I need to do, to get my code work correctly?

2
  • It seems odd an opcode that returns bytes memory. I think most opcodes returns a fixed amount of data. Those that return a variable amount of data expect an output memory pointer to be passed as parameter.
    – Ismael
    Feb 25 at 20:44
  • Yeah, I found out that such opcodes as call or delegatecall are use to return bytes memory. But I can't understand compiler code for them. They have last two arguments as outoffset and outsize. But as I said that hard to me to understand it code. Feb 26 at 7:02
0

I found a solution, it certainly looks stupid, but this case is already provided by the developers. All you need is utils().returnDataToArray().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.