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:
- 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).
- 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
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
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
Substituindo as variáveis na fórmula, temos:
userRewardAmount = sum(rewardPerStep[i] * userStakeAmount[i] / totalStakeAmount[i]), onde i — etapas de distribuição de recompensa
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
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])
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
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]
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;
}
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;
}
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
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)