WEB3DEV

Cover image for Por que o Contrato de Farming da PancakeSwap é Brilhante
Panegali
Panegali

Posted on

Por que o Contrato de Farming da PancakeSwap é Brilhante

As farms da PancakeSwap permitem que você ganhe uma renda extra na forma de tokens Cake para fazer staking de tokens Lp (provedores de liquidez). Este artigo não é sobre como fazer isso de forma mais eficiente. Neste artigo, darei uma olhada no contato inteligente de farming e na matemática por trás dele.

Enquanto trabalhava como desenvolvedor de contratos inteligentes, consegui escrever diferentes variações de staking, mas globalmente elas podem ser divididas em 2 tipos:

  1. Cada usuário recebe uma certa porcentagem da recompensa, acumulada com ou sem reinvestimento. As recompensas geralmente são pagas de um determinado pool, e os usuários escolhem o tempo pelo qual fazem stake dos tokens (quanto mais tempo, maior a porcentagem que receberão).
  2. O usuário recebe uma parte da recompensa proporcional à sua participação no número total de tokens em stake. A cada período de tempo (1 segundo, 1 bloco, 1 dia, etc.), um certo número de tokens são distribuídos. O farming da Pancake pertence ao segundo tipo.

Um pouco de matemática

Considere a fórmula para acumular tokens de acordo com as regras especificadas. Primeiramente, precisamos saber quanto do total o usuário possui:

userPercent = userStakeAmount / totalStakeAmount
Enter fullscreen mode Exit fullscreen mode

O usuário, a cada distribuição da recompensa, deverá receber esta parte do número distribuído de tokens, portanto, a cada distribuição, receberá:

userRewardStepAmount = rewardPerStep * userPercent
Enter fullscreen mode Exit fullscreen mode

Portanto, no momento da retirada, o usuário deve receber uma quantidade de tokens igual à soma das recompensas distribuídas a ele a cada etapa da distribuição. Então o usuário receberá:

userRewardAmount = sum(userRewardStepAmount[i]), onde i  etapas de distribuição de recompensa
Enter fullscreen mode Exit fullscreen mode

Substituindo as variáveis ​​na fórmula, temos:

userRewardAmount = sum(rewardPerStep[i] * userStakeAmount[i] / totalStakeAmount[i]), onde i  etapas de distribuição de recompensa
Enter fullscreen mode Exit fullscreen mode

Assim, para que os usuários tenham suas recompensas acumuladas, a cada etapa da distribuição é necessário passar por cada usuário dentro do loop e calcular seu rewardAmount no momento atual. Assim, se tivermos 10 usuários, ainda são números aceitáveis, porém, com o aumento do número de usuários, esse número vai crescer e, com isso, grandes somas serão gastas apenas na manutenção do sistema. Em redes de segunda camada (L2), tal solução provavelmente funcionará devido ao baixo custo dos tokens nativos, mas em ETH esses custos nem permitirão o lançamento do projeto.

O que a PancakeSwap fez

Para eliminar o loop e reduzir o cálculo da recompensa a um tempo constante, a Pancake alterou a fórmula de cálculo da recompensa. Vamos considerar a última fórmula com mais detalhes.

userRewardAmount = sum(rewardPerStep[i] * userStakeAmount[i] / totalStakeAmount[i]), onde i  etapas de distribuição de recompensa
Enter fullscreen mode Exit fullscreen mode

Das variáveis ​​temos rewardPerStep e totalStakeAmount, já que outros usuários podem depositar e sacar tokens. Consideraremos userStakeAmount como uma constante, pois com qualquer alteração nela, a recompensa acumulada pode ser retirada / salva (final do intervalo atual) e, a seguir, serão realizadas provisões para o userStakeAmount alterado (início de um novo intervalo). Portanto, este termo pode ser retirado para a soma:

userRewardAmount = userStakeAmount * sum(rewardPerStep[i] / totalStakeAmount[i])
Enter fullscreen mode Exit fullscreen mode

Vamos mover a soma da fórmula acima para uma variável separada — accCakePerShare. Ela manterá a soma de todos rewardPerStep[i] / totalStakeAmount[i], desde a primeira etapa de distribuição até a última. Então, entre quaisquer alterações em rewardPerStep e totalStakeAmount, este termo será uma constante. Por exemplo, não permita que ninguém deposite ou retire tokens por n segundos (recompensa distribuída a cada segundo) e que o rewardPerStep não mude. Então, para esses n segundos:

userRewardAmount = userStakeAmount * (n * rewardPerStep / totalStakeAmount) = userStakeAmount * accCakePerShare
Enter fullscreen mode Exit fullscreen mode

accCakePerShere só muda quando o usuário muda o tamanho de seu stake (depósito ou retirada de tokens), e esse é um ponto muito importante! A Pancake implementou esses métodos (stake, retirada) para recalcular accCakePerShere adicionando a ele n * rewardPerStep / totalStakeAmount, onde n — quantidade de etapas (segundos no exemplo anterior) desde a última atualização. Assim, multiplicando 3 termos (operação constante), os tokens de recompensa são creditados a todos os participantes do staking!

Mas há mais um problema a resolver. Como o accCakePerShare armazena todos os valores desde o início da distribuição, é necessário excluir adicionalmente os valores nos quais o usuário não participou da distribuição. Para isso, quando o stake do usuário muda, registramos o valor de accRewardPerShare no momento desse evento e posteriormente transferimos para o usuário apenas uma parte igual a:

userRewardAmount = userStakeAmount * (accRewardPerShere[end]  accRewardPerShere[start]) = userStakeAmount * sum(rewardPerStep[i] / totalStakeAmount[i]), onde i = (start : end]
Enter fullscreen mode Exit fullscreen mode

Foi assim que a Pancake reduziu o acúmulo de recompensas para o usuário a uma complexidade constante!

Trechos da implementação

Considere a implementação de farms da PancakeSwap.

Para começar, a Pancake apresenta pools de recompensas - pools diferentes para tokens diferentes. É aqui que o accCakePerShere é armazenado, bem como o registro de data e hora da última atualização.

struct Pool {
   IBEP20 lpToken; // token de stake apoiado
   uint256 allocPoint; // qual parte de todo rewardPerBlock este pool receberá
   uint256 accCakePerShare;
   uint256 lastUpdateBlock;
}
Enter fullscreen mode Exit fullscreen mode

Como os contratos não sabem trabalhar com números fracionários para preservar a precisão e reduzir erros na aritmética inteira, accRewardPerShere armazena o valor da soma descrita acima multiplicado por 10¹². Usei valores de 1024 em meus contratos para armazenar mais casas decimais (uint256 permite armazenar valores até 10⁷⁶ para não haver perigo de overflow).

Há também uma estrutura para o usuário. Ele armazena o tamanho do stake, o valor de rewardDebt = accRewardPerShere * valor no momento em que o valor do usuário foi alterado pela última vez (isso faz parte dos tokens distribuídos nos quais o usuário ainda não participou, como expliquei antes).

struct UserInfo {
   uint256 quantidade;
   uint256 rewardDebt;
   uint256 boostMultiplier;
}
Enter fullscreen mode Exit fullscreen mode

Na versão mais recente, a PancakeSwap adicionou a capacidade de impulsionar. O usuário ganha mais recompensa pelo fato de seu montante praticamente aumentar várias vezes (dependendo do valor do multiplicador). Assim, o stake do usuário é uma parcela maior do total do que sem o aumento, portanto, o usuário recebe mais recompensas. Com qualquer alteração no montante do usuário, o número de tokens recebidos é enviado a ele e o rewardDebt é atualizado até esse ponto (porque o usuário já retirou a recompensa do período anterior). Em meus contratos, adicionei o campo accReward na estrutura do usuário para salvar a recompensa já recebida pelo usuário ao alterar seu valor, porque eles bloqueiam a retirada por um determinado período.

O método updatePool é chamado antes de cada alteração no tamanho do stake por qualquer usuário. Ele atualiza accRewardPerShare para o estado atual, conforme indicado na fórmula abaixo:

accRewardPerShare += (block.number  lastUpdateBlock) * rewardPerStep / totalStakeAmount
Enter fullscreen mode Exit fullscreen mode

Esquemas de Auto-farmbacking

Do acima exposto, conclui-se que a recompensa é atribuída ao usuário apenas pelo valor do stake depositado por ele, portanto, neste esquema, não há possibilidade de calcular a recompensa levando em consideração o reinvestimento. Infelizmente, essa é a limitação com a qual você tem que lidar com o baixo custo de calcular a recompensa. No entanto, existem maneiras de implementar o mecanismo de reinvestimento manualmente. Para fazer isso, basta sacar a recompensa uma vez em um determinado período (uma vez por dia/semana/mês) e fazer stake do montante da recompensa acumulada e o stake inicial. A única condição é que o aumento total dos lucros compense os custos de reinvestimento.

Para resolver este problema, foram criadas plataformas para auto-farming. Essas plataformas são um conjunto de contratos inteligentes e um back-end. Um usuário que deseja receber renda de farming com reinvestimento de seus tokens em stake, não no farming original, mas no contrato inteligente desta plataforma. Este contrato coleta todos os tokens do usuário e fará stake em seu próprio nome em um contrato inteligente de farming. Assim, o contrato inteligente da plataforma pode gerenciar a stake geral. O back-end é responsável pelo mecanismo de reinvestimento. Ele invoca os métodos de contrato inteligente da plataforma, que executam ações semelhantes ao reinvestimento manual descrito acima. No entanto, desta vez, a retirada e o stake ocorrem em uma transação para um grande número de usuários, o que permite reduzir o custo de reinvestimento por N vezes, onde N é o número de participantes do auto-farming. No entanto, o back-end paga pelas operações de reinvestimento e, para cobrir os custos, o back-end troca parte da recompensa total recebida nativamente.

Dessa forma, os usuários recebem lucros maiores, pois o tamanho de seu stake está crescendo constantemente e, portanto, recebem uma parcela maior da recompensa distribuída.

Resumo

O farming da PancakeSwap é um dos padrões populares de design de contrato inteligente e não é surpreendente, considerando como reduziu a complexidade de calcular a recompensa em um loop de comprimento indeterminado para um constante!


Artigo escrito por WildMaus e traduzido por Marcelo Panegali.

Latest comments (0)