WEB3DEV

Cover image for Mergulho Profundo no Contrato de Staking da Sushiswap
Panegali
Panegali

Posted on

Mergulho Profundo no Contrato de Staking da Sushiswap

Dos protocolos de governança aos recursos de staking

Olá amigos, eu sou Wilson, seu velho amigo da bubble tree. Hoje vou mostrar algo interessante sobre Blockchain. Todos nós sabemos que a blockchain ganhou mais popularidade ao longo dos anos. Uma das aplicações interessantes em blockchain são as DEX (exchanges descentralizadas). A Uniswap, uma das principais plataformas DEX baseadas no modelo AMM (Formador de mercado), tem um volume diário de negociação de mais de 500 milhões. (A fórmula matemática sob a uniswap v2 e a uniswap v3 é linda, abordarei isso em uma série de artigos posteriores) e a Sushiswap é o projeto bifurcado construído sobre a uniswap V2. Hoje vamos passar por alguns dos contratos da Sushiswap e esperamos que depois de ler este artigo, você tenha um bom entendimento sobre o mecanismo de staking da Sushiswap.

Olhando de forma geral, podemos pensar que a estrutura de staking da sushiswap está organizada como o gráfico que desenhei abaixo. Os três contratos - sushiToken.sol, Masterchef.sol e Masterchef.sol, formam todos os recursos de staking.

1

Sushitoken.sol é o recurso chave para a governança da Sushiswap. Tem o mecanismo de votação para votar em diferentes propostas. (incluindo decidir a alocação de stakings para diferentes pools de negociação).

2

A maior parte do conteúdo do contrato é retirada de protocolos compostos - https://github.com/compound-finance/compound-protocol/blob/master/contracts/Governance/Comp.sol (as pequenas alterações que a Sushiswap fez neste contrato têm problemas e abordaremos isso em séries posteriores).

No Sushitoken.sol, a estrutura de dados para registrar os votos é checkPoints.

/// @notice Um ponto de verificação para marcar o número de votos de um determinado bloco
struct Checkpoint {
uint32 fromBlock;
uint256 votes;
}
Enter fullscreen mode Exit fullscreen mode

Sushitoken.sol usa o modelo delegado para contar os votos. vamos examinar abaixo a função _delegate. ele invocaria o _moveDelegates para reescrever o número de votação no delegado antigo e no novo delegado. (moveDelegates então acionaria _writeCheckpoint que faz a gravação final do checkPoint). Meus comentários abaixo fornecem o mecanismo completo de como funciona o modelo delegado.

function _delegate(address delegator, address delegatee)
internal
{ ////obter o delegado atual deste delegador
address currentDelegate = _delegates[delegator];
//obter o saldo deste delegador
uint256 delegatorBalance = balanceOf(delegator); // saldo de SUSHIs subjacentes (não escalado);
//atribuir novo delegado a este delegante
_delegates[delegator] = delegatee;
emit DelegateChanged(delegator, currentDelegate, delegatee);
//o poder de voto também precisa ser trocado.
_moveDelegates(currentDelegate, delegatee, delegatorBalance);
}
Enter fullscreen mode Exit fullscreen mode
function _moveDelegates(address srcRep, address dstRep, uint256 amount) internal {
//parâmetro de valor de entrada é o saldo do delegante
if (srcRep != dstRep && amount > 0) {
if (srcRep != address(0)) {
// tudo o que esse bloco de código faz é diminuir o poder de voto do delegado original
//obter o número de votos deste delegado
uint32 srcRepNum = numCheckpoints[srcRep];
uint256 srcRepOld = srcRepNum > 0 ? checkpoints[srcRep][srcRepNum - 1].votes : 0;
//subtrair o montante (saldo total deste delegador) do delegado original
//isso é problemático, pois trará a questão da duplicação de votos, cobriremos isso mais tarde
uint256 srcRepNew = srcRepOld.sub(amount);
_writeCheckpoint(srcRep, srcRepNum, srcRepOld, srcRepNew);
}
if (dstRep != address(0)) {
// aumentar o poder de voto para o novo delegado
uint32 dstRepNum = numCheckpoints[dstRep];
uint256 dstRepOld = dstRepNum > 0 ? checkpoints[dstRep][dstRepNum - 1].votes : 0;
uint256 dstRepNew = dstRepOld.add(amount);
_writeCheckpoint(dstRep, dstRepNum, dstRepOld, dstRepNew);
}
}
}
Enter fullscreen mode Exit fullscreen mode

Outra coisa para prestar atenção neste contrato é a função abaixo. O direito de cunhagem é com o contrato Masterchef (este é um fluxo de projeto significativo que abordaremos em séries posteriores). O Masterchef chamará a função mint para cunhar e também acionará a função _moveDelegates

/// @notice Cria token `_amount` para `_to`. Só deve ser chamado pelo proprietário (MasterChef).
function mint(address _to, uint256 _amount) public onlyOwner {
_mint(_to, _amount);
_moveDelegates(address(0), _delegates[_to], _amount);
}
Enter fullscreen mode Exit fullscreen mode

Então, como está sendo usado o resultado da votação em nossa estrutura de dados checkPoints? Então, examine o contrato Masterchef.sol.

A princípio, fiquei confuso sobre como funciona, pois não encontro a estrutura de dados do checkPoint no Masterchef.sol. Mas então descobri a função abaixo e entendo como o checkPoint está sendo usado. Ao configurar o par de negociação, a equipe da Sushiswap configura manualmente como o _allocPoint no contrato do masterchef, onde o _allocPoint é o resultado da votação do checkPoint. Então agora está tudo claro? Este processo, no entanto, não é transparente para o usuário. (No caso ideal, queremos que o ponto de verificação seja transferido automaticamente e usado no contrato “masterchef”)

function set(
uint256 _pid,
uint256 _allocPoint,
bool _withUpdate
) public onlyOwner {
if (_withUpdate) {
massUpdatePools();
}
//quando atualizamos o ponto de alocação para um pool específico, precisamos atualizar o ponto de alocação total para refletir o fato de que allocPoint mudaria o totalAllocPoint
totalAllocPoint = totalAllocPoint.sub(poolInfo[_pid].allocPoint).add(
_allocPoint
);
poolInfo[_pid].allocPoint = _allocPoint;
}
Enter fullscreen mode Exit fullscreen mode

Abaixo estão algumas outras funções interessantes do Masterchef, o updatePool está sendo acionado no deposit, withdraw e massUpdatePools para atualizar o sushi e também a cunhagem (o mecanismo de cunhagem é muito ligado ao contrato do Masterchef, por isso é difícil fazer alterações. Esta é outra desvantagem do contrato Masterchef)

function updatePool(uint256 _pid) public {
PoolInfo storage pool = poolInfo[_pid];
if (block.number <= pool.lastRewardBlock) {
return;
}
uint256 lpSupply = pool.lpToken.balanceOf(address(this));
if (lpSupply == 0) {
pool.lastRewardBlock = block.number;
return;
}
uint256 multiplier = getMultiplier(pool.lastRewardBlock, block.number);
//calcular a recompensa do sushi
uint256 sushiReward =
multiplier.mul(sushiPerBlock).mul(pool.allocPoint).div(
totalAllocPoint
);
// implementação da cunhagem do sushi
sushi.mint(devaddr, sushiReward.div(10));
sushi.mint(address(this), sushiReward);
//atualize o accSushiPerShare por considerar o sushiReward
pool.accSushiPerShare = pool.accSushiPerShare.add(
sushiReward.mul(1e12).div(lpSupply)
);
//atualize o lastRewardBlock
pool.lastRewardBlock = block.number;
}
Enter fullscreen mode Exit fullscreen mode

A função getMultiplier é outra função interessante, a equipe de Sushiswap incentiva o contribuidor inicial. Então no início do contrato, o contrato do Masterchef possui a variável uint256 t BONUS_MULTIPLIER = 10 sendo declarada, a qual será utilizada posteriormente pela função getMultiplier. Tudo o que a função getMultiplier está fazendo é se todos os intervalos estiverem dentro do bonusEndBlock, os participantes recebem toda a recompensa extra. Se o ponto de partida já ultrapassou o bonusEndBlock, os participantes não recebem nada, se houver alguma parte dentro do bonusEndBlock, então o _to subtract bonusEndblock é a parte que não deve receber a recompensa extra.

// Retornar o multiplicador de recompensa sobre o bloco _from para o.
function getMultiplier(uint256 _from, uint256 _to)
public
view
returns (uint256)
{
if (_to <= bonusEndBlock) {
return _to.sub(_from).mul(BONUS_MULTIPLIER);
} else if (_from >= bonusEndBlock) {
//sem multiplicador de bônus, neste caso
return _to.sub(_from);
} else {
//calculamos apenas a parte do bloco final de bônus para dar o multiplicador de bônus, não damos este multiplicador para o resto
return
bonusEndBlock.sub(_from).mul(BONUS_MULTIPLIER).add(
_to.sub(bonusEndBlock)
);
}
}
Enter fullscreen mode Exit fullscreen mode

Por fim, revise o contrato masterchef 2. Porque Masterchef é o contrato que detém o direito de cunhagem e o contrato foi implantado na blockchain. A equipe da Sushiswap só pode criar o código abaixo para depositá-lo no ID masterchef original (esse é o grande inconveniente e torna o contrato do Masterchef 2 vago). Em uma série de artigos posteriores, fornecerei a solução do mecanismo que pode resolver esse problema.

/// @notice Deposita um token falso (dummy token) para `MASTER_CHEF` MCV1. Isto é necessário porque MCV1 detém os direitos de cunhagem para SUSHI.
/// Qualquer saldo do remetente da transação no `dummyToken` é transferido.
/// O ponto de alocação para o pool em MCV1 é o ponto de alocação total para todos os pools que recebem incentivos duplos.
/// @param dummyToken O endereço do token ERC-20 a ser depositado no MCV1.
function init(IERC20 dummyToken) external {
uint256 balance = dummyToken.balanceOf(msg.sender);
require(balance != 0, "MasterChefV2: O saldo deve exceder 0");
dummyToken.safeTransferFrom(msg.sender, address(this), balance);
dummyToken.approve(address(MASTER_CHEF), balance);
MASTER_CHEF.deposit(MASTER_PID, balance);
emit LogInit();
}
Enter fullscreen mode Exit fullscreen mode

Uma das coisas boas do masterchef 2 é que eles têm uma interface flexível para implementar a recompensa. Por exemplo, abaixo está a função onesushiReward encapsulada, que também fornece alguma economia de gás ao atualizar o rewardDebt todas as vezes em comparação com o contrato Masterchef.

function onSushiReward (uint256 pid, address _user, address to, uint256, uint256 lpToken) onlyMCV2 lock override external {
PoolInfo memory pool = updatePool(pid);
UserInfo storage user = userInfo[pid][_user];
uint256 pending;
if (user.amount > 0) {
pending =
(user.amount.mul(pool.accSushiPerShare) / ACC_TOKEN_PRECISION).sub(
user.rewardDebt
).add(user.unpaidRewards);
uint256 balance = rewardToken.balanceOf(address(this));
if (pending > balance) {
rewardToken.safeTransfer(to, balance);
user.unpaidRewards = pending - balance;
} else {
rewardToken.safeTransfer(to, pending);
user.unpaidRewards = 0;
}
}
user.amount = lpToken;
user.rewardDebt = lpToken.mul(pool.accSushiPerShare) / ACC_TOKEN_PRECISION;
emit LogOnReward(_user, pid, pending - user.unpaidRewards, to);
}
Enter fullscreen mode Exit fullscreen mode

Para concluir, este artigo passou pelo mecanismo de staking da Sushiswap,

E apontou algumas desvantagens do contrato original da Sushiswap (não transparente e flexível em termos de checkPoint, problema de cunhagem de direitos, contagem de votos imprecisa porque usa o saldo). No entanto, esses não são apenas problemas associados ao contrato da Sushiswap, o outro problema inclui os problemas de desvio de liquidez (a alocação do sushireward pode ser concentrada em poucos pools principais, portanto, deve haver um mecanismo de votação melhor) e problemas de pool de lixo (o pool criado não tem liquidez suficiente, os outros protocolos, como Pancake alocariam recompensas maiores nesses pools, mas isso não resolve os problemas fundamentais). Em uma série de artigos posteriores, mostrarei a exploração que fiz para resolver esses problemas


Artigo escrito por Bubbletree e traduzido por Marcelo Panegali

Latest comments (0)