Como determinar o endereço de um contrato inteligente antes da implantação: usando CREATE2 para uma troca de criptografia

O tema blockchain não deixa de ser fonte não apenas de todo tipo de hype, mas também de ideias muito valiosas do ponto de vista tecnológico. Portanto, ela não contornou os habitantes da cidade ensolarada. As pessoas estão olhando de perto, estudando, tentando transferir seus conhecimentos na infobase tradicional para sistemas blockchain. Até agora, pontualmente: um dos desenvolvimentos da Rostelecom-Solar é capaz de verificar a segurança de softwares baseados em blockchain. E ao longo do caminho, surgem algumas reflexões sobre a solução de problemas aplicados da comunidade blockchain. Um desses hacks - como determinar o endereço de um contrato inteligente antes da implantação usando CREATE2 - hoje quero compartilhar com vocês os detalhes.

Como determinar o endereço de um contrato inteligente antes da implantação: usando CREATE2 para uma troca de criptografia
O opcode CREATE2 foi adicionado no hard fork Constantinople em 28 de fevereiro deste ano. Conforme declarado no EIP, este opcode foi introduzido principalmente para canais de estado. No entanto, usamos isso para resolver um problema diferente.

Existem usuários com saldo na exchange. Devemos fornecer a cada usuário um endereço Ethereum, para o qual qualquer pessoa possa enviar tokens, reabastecendo assim sua conta. Vamos chamar esses endereços de “carteiras”. Quando os tokens chegam nas carteiras, devemos enviá-los para uma única carteira (hotwallet).

Nas seções a seguir, analiso opções para resolver esse problema sem CREATE2 e explico por que as abandonamos. Se você está interessado apenas no resultado final, poderá encontrá-lo na seção Solução Final.

Endereços Ethereum

A solução mais simples é gerar novos endereços Ethereum para novos usuários. Esses endereços serão as carteiras. Para transferir tokens da carteira para o hotwallet, você precisa assinar a transação chamando a função transferir() com a chave privada da carteira do backend.

Essa abordagem tem as seguintes vantagens:

  • é só
  • o custo de transferência de tokens da carteira para o hotwallet é igual ao custo de chamar a função transferir()

No entanto, abandonamos essa abordagem porque ela tem uma desvantagem significativa: você precisa armazenar as chaves privadas em algum lugar. E não é apenas que eles podem ser perdidos, mas também que você precisa gerenciar cuidadosamente o acesso a essas chaves. Se pelo menos um deles estiver comprometido, os tokens de um determinado usuário não chegarão à carteira quente.

Como determinar o endereço de um contrato inteligente antes da implantação: usando CREATE2 para uma troca de criptografia

Crie um contrato inteligente separado para cada usuário

A implantação de um contrato inteligente separado para cada usuário permite que você não armazene chaves privadas de carteiras no servidor. A exchange chamará esse contrato inteligente para transferir os tokens para o hotwallet.

Também abandonámos esta solução, uma vez que não é possível mostrar ao utilizador o endereço da sua carteira sem implementar um contrato inteligente (isto é realmente possível, mas de uma forma bastante complicada com outras desvantagens que não discutiremos aqui). Na exchange, o usuário pode criar quantas contas precisar, e cada um precisa de sua carteira. Isso significa que precisamos gastar dinheiro na implantação de um contrato, mesmo sem ter certeza de que o usuário utilizará essa conta.

Código de operação CREATE2

Para resolver o problema do método anterior, decidimos usar o opcode CREATE2. CREATE2 permite pré-determinar o endereço onde o contrato inteligente será implantado. O endereço é calculado usando a seguinte fórmula:

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]


, onde:

  • endereço — endereço do contrato inteligente que chamará CREATE2
  • sal - valor aleatório
  • código_inicial - bytecode de contrato inteligente para implantação

Isso garante que o endereço que fornecemos ao usuário conterá realmente o bytecode desejado. Além disso, este contrato inteligente pode ser implantado sempre que precisarmos. Por exemplo, quando um usuário decide usar sua carteira pela primeira vez.
Como determinar o endereço de um contrato inteligente antes da implantação: usando CREATE2 para uma troca de criptografia
Além disso, você pode calcular o endereço do contrato inteligente a cada vez, em vez de armazená-lo, porque:

  • endereço na fórmula é constante pois é o endereço da nossa fábrica de carteiras
  • sal - hash user_id
  • código_inicial é permanente, pois usamos a mesma carteira

Mais melhorias

A solução anterior ainda tem uma desvantagem: você precisa pagar pela implantação do contrato inteligente. No entanto, você pode se livrar disso. Para isso você pode chamar a função transferir()e depois Auto-destruição() no construtor da carteira. E então o gás para implantação do contrato inteligente será devolvido.

Ao contrário da crença popular, você pode implantar um contrato inteligente no mesmo endereço várias vezes com o opcode CREATE2. Isso ocorre porque CREATE2 verifica se o nonce do endereço de destino é zero (é atribuído o valor "1" no início do construtor). Ao mesmo tempo, a função Auto-destruição() redefine o endereço nonce todas as vezes. Assim, se você chamar CREATE2 novamente com os mesmos argumentos, a verificação do nonce será aprovada.

Observe que esta solução é semelhante à solução de endereço Ethereum, mas sem a necessidade de armazenar chaves privadas. O custo de transferir dinheiro de uma carteira para uma hotwallet é aproximadamente igual ao custo de chamar uma função transferir(), uma vez que não pagamos pela implantação do contrato inteligente.

Decisão final

Como determinar o endereço de um contrato inteligente antes da implantação: usando CREATE2 para uma troca de criptografia

Inicialmente preparado:

  • função para obter sal user_id
  • contrato inteligente que chamará o opcode CREATE2 com o sal apropriado (ou seja, fábrica de carteira)
  • bytecode da carteira correspondente ao contrato com o seguinte construtor:

constructor () {
    address hotWallet = 0x…;
    address token = 0x…;
    token.transfer (hotWallet, token.balanceOf (address (this)));
    selfdestruct (address (0));
}


Para cada novo usuário, mostramos o endereço de sua carteira calculando

keccak256 (0xff ++ address ++ salt ++ keccak256 (init_code)) [12:]


Quando o usuário transfere tokens para o endereço da carteira correspondente, nosso backend vê o evento Transfer com o parâmetro _paraigual ao endereço da carteira. Neste ponto já é possível aumentar o saldo do usuário na exchange antes de implantar a carteira.

Quando tokens suficientes se acumularem no endereço da carteira, poderemos transferi-los todos de uma vez para o hotwallet. Para fazer isso, o backend chama a função de fábrica de contrato inteligente, que executa as seguintes ações:

function deployWallet (соль uint256) {
    bytes memory walletBytecode =…;
    // invoke CREATE2 with wallet bytecode and salt
}


Assim, é chamado o construtor do contrato inteligente da carteira, que transfere todos os seus tokens para o endereço do hotwallet e depois se autodestrói.

O código completo pode ser encontrado aqui. Observe que este não é o nosso código de produção, pois decidimos otimizar o bytecode da carteira e escrevê-lo em opcodes.

Autor Pavel Kondratenkov, especialista em Ethereum

Fonte: habr.com

Adicionar um comentário