0

Somebody could explain how CODECOPY works in practice ? There is only one sample in evm.playground but I dont understand above-mentioned sample. Could you give me another concrete sample (with playground) ?

Sincerely Engin YILMAZ

1
  • 1
    the instruction is very clear, it will copy code of the contract to memory, what examples do you need? You may hardcode constants in your contract code and then copy it to memory for further use, that's one example
    – Nulik
    Commented Jan 26, 2023 at 15:13

1 Answer 1

1

I've crafted a kind of "Hello world" contract, a contract that simply returns the value 7. I found out that I needed to use CODECOPY for that. I had to learn myself what tools to use, if anybody has any tool-suggestions while seeing my code, I'd be happy. I've encapsulated the evm opcode inside a Python program to compile it, but if you don't know Python you can just look at the opcodes:

from pyevmasm import assemble_hex

assembly_code = """
// Constructor. This is executed once when the contract is deployed to the blockchain.
PUSH1 0x29      // 41 bytes is the length of the contract below, which  needs to be put on the blockchain // (2 bytes)
PUSH1 0xc       // starting byte of the contract below // (2 bytes)
PUSH1 0x0       // Offset in memory // (2 bytes)
CODECOPY        // Put the contract below into memory // (1 byte)
PUSH1 0x29      // length of the contract below that's now stored in memory // (2 bytes)
PUSH1 0x0       // Offset in memory of the contract below // (2 bytes)
// RETURN has a slightly different meaning during deployment than in a normal call to the contract.
// Here during the deployment, the return value will be the code of the contract that we're deploying. 
RETURN          // Return the code of the contract and end execution of the deployment // (1 byte)

// Deployed Contract on blockchain
PUSH32 0x7      // The value 7    // (33 bytes, currently at byte 12 = 0xc)
PUSH1 0x40      // Memory address at which to start storing   // (2 bytes)
MSTORE          // Store the value 7 at memory address 0x40 // (1 byte)
PUSH1 0x20      // Length of return data: 32 bytes  // (2 bytes)
PUSH1 0x40      // Start of return data   // (2 bytes)
RETURN          // return the value 7 to the caller of this contract // (1 byte)

"""

def remove_comments(code):
    lines = code.split('\n')  # Split the code into lines
    stripped_lines = []

    for line in lines:
        # Split each line on '//', take the first part, and strip whitespace
        stripped_line = line.split('//')[0].strip()
        stripped_lines.append(stripped_line)

    # Join the stripped lines back together with newline characters
    stripped_code = '\n'.join(stripped_lines)

    return stripped_code


bytecode = assemble_hex(remove_comments(assembly_code))
print(bytecode)

To verify that the contract is correct, I've created a Hardhat test that deploys the bytecode and then calls the contract and prints the return value:

const { ethers } = require("hardhat");

// self-containing hardhat test. It includes the bytecode program that's "tested".

describe('MyContract', () => {
  let deployer;
  let ContractFactory;
  let receiver;
  let contract;

  before(async function() {
    this.timeout(10000)
    // bytecode for the contract (result of the Python program above)
    let bytecode = '0x6029600c60003960296000f37f000000000000000000000000000000000000000000000000000000000000000760405260206040f3';

    ContractFactory = await ethers.getContractFactory([], bytecode); // []: we provide no abi
    contract = await ContractFactory.deploy({gasLimit: 8000000});
    await contract.deployed();
    // Get the contract deployer account
    [deployer, receiver] = await ethers.getSigners();
  });

  it('do something', async function() {

    const result = await deployer.sendTransaction({
      to: contract.address,
      data: 0x20  // Input data. Since our contract ignores this, this can be anything
    });
    const receipt = await result.wait(); // Wait for transaction to be mined

    // Decode the output from the contract
    if (receipt && receipt.logs && receipt.logs.length > 0) {
      // This code prints the emitted events (with LOG or LOGx)
      receipt.logs.forEach(log => {
        console.log(log.data);
      });
    } else {
      // If there was no emitted event, let's make another call but now catch the return value.
      // This is the path that's currently taken.
      // todo: Skip the previous call (deployer.sendTransaction)
      const callResult = await ethers.provider.call({
        to: contract.address,
        data: '0x20'
      });
      console.log(callResult);
    }
  });
});
2
  • 1
    To add some further clarification on that, every contract uses CODECOPY to deploy themselves. Basically, when a transaction is sent without a recipient specified (that's what a contract deployment is), the transaction data is interpreted as code, this code is executed, and whatever this code returns is stored on chain. So the most simple contract deployer would be pretty much what you wrote : CODECOPY everything after itself, so it's in memory, => return all of the memory.
    – Foxxxey
    Commented Jun 18, 2023 at 15:53
  • 1
    And yeah, there are tools to make writing EVM assembly easier, you might want to take a look at the EVM toolkit (quilt.github.io/etk) pretty sure it's the only one to support labels (and macros, apparently), which is pretty nice
    – Foxxxey
    Commented Jun 18, 2023 at 15:54

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

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