Introdução:
Olá, Eu sou o Eugene e esta é a segunda parte da série de análises da live contract, e hoje vamos analisar o staking pool contract que está sendo utilizado neste exato momento para proteger o sistema de prova de participação do “NEAR Protocol”. Basicamente todos os validadores que atualmente estão em marcha na NEAR Protocol estão sendo executados em nome deste contrato. Eles não controlam por si mesmos a conta onde está retida a quantia de tokens da NEAR requeridos para prova de participação. Em vez disso, o contrato que retêm essa quantia, somente proporciona um grupo de staking e executa seus nodes. Hoje examinaremos a fundo esse contrato. Nos contratos principais temos uma staking pool contract que é um pouco mais complicada que o contrato que analisamos previamente (Voting Contract). Assim que hoje vamos nos focar mais na lógica e menos na near_bridgen e detalhes específicos da Rust, mas isso provavelmente envolverá um pouco mais de conhecimento do protocolo NEAR. Aqui temos o staking pool contract no NEAR Github. Abaixo está o vídeo original no qual está baseado esta guia.
lib.rs
Constructor Structure
Como sempre, o contrato começa desde a estrutura principal. E neste caso é uma estrutura de staking contract. Como pode-se ver, aqui temos near_bidgen, BorshSerialize e there’s near_bindgen,BorshSerialize e BorshDeserializeand. A estrutura, tem agora, muitos mais campos que anteriormente, também tem muitos comentários sobre tais campos e provavelmente muitos deles já estejam atualizados. A lógica do contrato de staking pool, nos permite o seguinte:
Basicamente, qualquer pessoa pode depositar uma determinada quantidade de Tokens da NEAR em uma staking pool e delegá-los ao staking pool, sendo parte do staking. Isso nos permite agrupar saldos de várias pessoas (nós a chamamos contas) em uma grande stake (participação) e desta forma este grande grupo pode então se qualificar para obter a licença de validador. NEAR Protocol atualmente tem um número limitado de licenças para cada shard, sendo no máximo cem (100) licenças de validador. Você pode aspirar a essas licenças da seguinte maneira: Se ao dividir o total de tokens por cem (100) o resultado deve ser a quantia mínima requerida para uma só licença. Com a excepção de que é um pouco mais complicado de envolver a remoção dos stakes que não estão qualificados para conter essa quantia mínima, etcetera. Esse contrato é basicamente um contrato autônomo sem nenhuma chave de acesso e controlado pelo seu dono. Em tal caso o proprietário é instruído no método de iniciação.
Método de Iniciação
Então vamos ao método de inicialização. Ele tem três sub-rotinas e a primeira é o owner_id, que é o id da conta do proprietário. O proprietário tem várias permissões neste contrato que permitem que o contrato execute ações que não estão disponíveis para o restante das contas. Um desses métodos era votar em nome do staking pool para o contrato de votação que discutimos na última vez. Assim, o proprietário pode solicitar o método de votação.
Verificamos então que o predecessor (predecessor_account_id) seja igual ao dono já que este método só pode ser solicitado pelo mesmo.
Então, o que o método de votação faz é verificar se o método foi solicitado unicamente pelo proprietário e, em seguida, verificar a coerência, mas isso não é importante neste momento.
Então o contrato é o dono, e esse dono ele tem permissões extras e pode fazer certas coisas. Portanto são necessários mais alguns campos: habilitar a stake_public_key. Ao fazer staking no protocolo NEAR, você precisa fornecer uma chave pública que será usada pelo node validador para assinar mensagens em nome do node validador. Esta chave pública pode ser diferente de qualquer outra chave de acesso, e a ideia é que essa chave de acesso deva ser diferente de qualquer outra chave de acesso, pois seu node pode ser executado em um data center que possivelmente seja vulnerável a alguns ataques. E no caso de um ataque o máximo que poderia ocorrer é algum dano na net mas não na sua conta. Seus fundos não poderão ser roubados e você pode substituir essa chave mais fácil do que você substitui uma chave de acesso inválida. Finalmente, a terceira sub-rotina que o contrato leva é o reward_fee_fraction inicial. Essa é a comissão que o proprietário do staking pool recebe para executar o node validador.
Esta é uma fração que tem um numerador e um denominador, e permite, por exemplo, que você diga basicamente “Eu pego 1% das recompensas por administrar esse pool específico”. Digamos que você tenha 1.000.000 tokens, os quais adquiriram alguma recompensa, e digamos que haja uma recompensa de 10.000 tokens, então o proprietário receberá 1% disso, que será de 100 tokens. Os floats têm um comportamento imprevisível quando você os multiplica. Por exemplo, com frações, você pode calcular com um maior número de bits. Por exemplo, a maneira como você faz a divisão, é primeiro multiplicar o valor que é u128 por um numerador (isso pode desbordar em u128), e é por esse motivo que o fazemos em u256. Então você divide pelo denominador que deve trazê-lo abaixo de u128 novamente. Isso lhe dá uma maior precisão do que o float64 que não pode operar com a mesma precisão do u128 bits, haverá alguns erros de arredondamento ou de precisão ao fazer as contas. Portanto, você precisa de floats de maior precisão, que não são muito diferentes das contas que simulamos com u256. O Solidity originalmente não suportava floats, e ao princípio nós também não demos suporte, mas isso gerou alguns problemas em torno da formatação de strings no Rust para depuração, e portanto decidimos que não é um problema suportar floats, especialmente porque o padronizamos no lado vm. O maior problema com os floats era o comportamento indefinido em torno de certos valores de cargas. Por exemplo, quais outros bits contêm quando você tem um float infinito? Nós padronizamos isso e agora eles são independentes mas de plataforma equivalentes. Portanto, já não há nenhum problema em usar floats em nosso ambiente vm.
A prática normal ou standard com init é que primeiro verificamos que não exista a condição, logo verificamos a entrada. A primeira coisa que faremos, é confirmar que a fração é válida e verificar que o denominador não seja zero. Em seguida, temos outra instrução que verifica se o numerador é menor ou igual ao denominador, o que significa que a fração é menor ou igual a 1. Isso é de importância para evitar alguns erros lógicos. Em seguida verificaremos que a conta é válida. Isso é de importância para evitar alguns erros lógicos. Em seguida verificaremos que a conta é válida. Por exemplo: Temos o ID de conta válido em formato JSON que faz essa verificação automaticamente durante a desserialização, se for inválido, entrará em pânico. Depois disso, puxamos o saldo da conta corrente do contrato de staking. Esse saldo geralmente é grande o suficiente porque temos que pagar pelo armazenamento deste contrato específico, e então avisamos que vamos alocar alguns tokens para o STAKE_SHARE_PRICE_GUARANTEE_FUND. O staking pool tem certas garantias que são importantes para os contratos locais. As garantias asseguram que, quando você depositar no staking pool, poderá sacar pelo menos a mesma quantidade de tokens e não poderá perder tokens nem mesmo por 1 000 000 000 000 yocto (10-24 ) NEAR neste contrato (NEAR on this contract), O fundo STAKE_SHARE_PRICE_GUARANTEE_FUND é de cerca de 1 trilhão de yocto NEAR, enquanto normalmente consumimos cerca de 1 ou 2 trilhões de yocto NEAR para erros de arredondamento. E finalmente, temos presente qual é o saldo que vamos depositar em stake a nome deste contrato. Isso é necessário para estabelecer qualquer linha de base para limitar as diferenças de arredondamento. Em seguida, verificamos se a conta não está no stake ainda.
Nós não queremos que isso aconteça, o que queremos é dar início ao contrato antes que isso aconteça. Finalmente inicializamos a estrutura, mas não a retornamos imediatamente. Acabamos de criar aqui, a :StakingContract structure.
Em seguida, emitimos uma transação de re-staking . Isso é importante, porque precisamos ter certeza de que essa “chave ristretto” restrita é uma chave válida, por exemplo, uma chave válida 5 119. Existem algumas chaves na curva que são chaves válidas, mas não são específicas de ristretto, e as chaves de validação só podem ser as específicas de ristretto. Isso é algo especificamente do protocolo da NEAR, e o que acontece, é que ele faz uma transação de staking com a chave fornecida.Uma vez que esta transação é criada a partir do contrato, damos validade a essa transação quando ela se executa. Se a chave for inválida, ela vai gerar um erro e toda a inicialização desse pool de staking falhará. Se você passar uma stake_public_key inválida como entrada, sua consolidação e implantação de contrato e tudo o que acontecer nessa transação em lote será revertido. Isso é importante para que o pool não tenha uma chave inválida, pois isso pode estar permitindo que você bloqueie a participação de outras pessoas. Como parte das garantias, estamos afirmando que, se você desfizer o stake, seus tokens serão devolvidos em 4 períodos. Eles serão elegíveis para retirada, e isso é importante para poder devolvê-los aos Lockups.
Penso que são demasiados detalhes para serem tomados em conta antes de explicar a visão geral de alto nível de como os contratos funcionam e como funcionam os saldos. Vamos explicar o conceito de como podemos distribuir recompensas aos proprietários de contas em tempo constante quando um período passa. Isso é importante para a maioria dos contratos inteligentes. Eles querem operar em tempo constante para cada método em vez de tempo linear para a quantidade de usuários, porque se a quantidade de usuários crescer, a quantidade de gás necessária para operar uma escala linear crescerá também, e eventualmente ficará sem gás. É por isso que todos os contratos inteligentes precisam operar em tempo constante.
Estrutura da Conta
Mantemos a estrutura chamada conta, do jeito que funciona para cada usuário. Todo usuário que transferir para este staking pool terá uma estrutura chamada account que possui os seguintes campos: unstaked é o saldo em yocto NEAR que não está em staking, portanto é apenas o saldo do usuário. Então stake_shares é na verdade um saldo, mas não em NEAR, e sim em número de ações de participação. Stake_shares é um conceito que foi adicionado especificamente a esse staking pool. A maneira como funciona é que quando você entra no stake pool, você basicamente compra novas ações ao preço atual, convertendo seu saldo fora do stake_pool em ações de participação. Um preço de participação é originalmente 1, mas com o tempo ele cresce com as recompensas e, quando a conta recebe recompensas, seu saldo total de participação aumenta, mas a quantidade total de participações não muda. Essencialmente, quando uma conta recebe recompensas de validação ou algum outro depósito direto no saldo, aumenta o valor que você pode receber por cada participação. Digamos, por exemplo, que você originalmente tinha 1 milhão NEAR que foi depositado nessa conta. Digamos que você receba 1 milhão de ações (ignorando o yocto NEAR por enquanto), se o staking pool recebeu 10.000 NEAR em recompensas, você ainda tem 1 milhão de ações, mas o 1 milhão de ações agora corresponde a 1.010.000 NEAR. Agora, se alguém quiser entrar no pool, nesse momento, eles vão comprar as ações de participação internamente dentro do contrato ao preço de 1,001 NEAR, porque cada ação agora vale isso. Quando você recebe outra recompensa, não precisa comprar mais ações apesar do saldo total, e no tempo constante todos dividem a recompensa proporcionalmente ao número de ações que possuem. Agora, quando você sai do pool, você está, na essência, vendendo essas ações ou queimando-as usando o conceito de tokens fungíveis em favor do saldo não fora do pool. Então, você vende ao preço atual, diminui o valor total da participação, bem como a quantidade total de ações, e quando você compra, aumenta o saldo total da participação e o total de ações da participação, mantendo o preço constante. Quando você entra ou sai do stake-pool, você não altera o valor, mas quando recebe as recompensas, aumenta ovalor.
Nota: Um epoch é uma unidade de tempo de aproximadamente ~12 horas ou 43,200 segundos para ser exatos.
O preço só pode subir, e isso pode ocasionar erros de arredondamento das contas quando o seu yocto NEAR não bater precisamente com o seu saldo. É por isso que temos esse fundo de garantia de 1 trilhão de yocto NEAR que colocará um yocto NEAR extra de vez em quando para bater as contas. E , a parte final está aí, pois o protocolo NEAR não para o stake e devolve o saldo imediatamente, tem que esperar três epochs até que seu saldo seja tirado do pool e devolvido à conta. Se você sair do pool (unstake), não poderá retirar esse saldo imediatamente do staking pool, precisará aguardar três epochs. Você vai ter que se lembrar em que momento você fez seu último pedido de unstake e, após três epochs, seu saldo será desbloqueado e você poderá fazer o saque do unstake. No entanto, há uma ressalva: se você fizer o unstake no último bloco do epoch, a ordem só será confirmada no seguinte epoch e a partir desse momento é que vamos contar os três epochs. A ordem chegará no primeiro bloco do próximo epoch, e isso atrasará seu saldo bloqueado a ser desbloqueado para quatro epochs em vez de três. Isso ocorre porque registramos o epoch no bloco anterior, mas a transação real só foi confirmada no bloco seguinte, no seguinte epoch. Para garantir que isso não aconteça, bloqueamos o saldo em quatro epochs em vez de três para poder contabilizar a separação de blocos. Isso é o que constitui uma conta. A ideia de ações não é tão nova, porque em Ethereum a maioria dos provedores de liquidez e os criadores de mercado automatizados, usam esse conceito semelhante. Quando você, por exemplo, deposita no pool de liquidez, obtém algum tipo de token desse pool em vez do valor real que ali está representado. Quando você faz o saque do liquidity pool, queima esse token e obtém os tokens reais representados. A ideia é muito parecida com chamá-las de ações, porque elas têm um preço correspondente, e poderíamos ter chamado de outra forma. Isso foi praticamente desde o início deste staking pool. Houve um estudo sobre como poder fazer isso corretamente, e uma maneira foi limitar o número de contas que podem depositar em uma determinada conta de pool para essa atualização específica. E eventualmente terminamos por chegar no tempo de complexidade constante e na verdade era um modelo bem mais simples. Então a matemática da estrutura stake_shares tornou-se um pouco mais razoável, mesmo assim existem alguns detalhes também.
Tipos de Contratos
Vamos vasculhar este contrato. Por exemplo, não está tão bem estruturado quanto um contrato de bloqueio, porque o bloqueio é ainda mais complicado. Os tipos ainda são agrupados no mesmo contrato. Existem vários modelos de tipos, por exemplo reward_fee_fraction é um modelo separado.
Conta é um tipo separado e também há uma conta legível por humanos que também é um tipo usado apenas para chamadas de exibição, portanto, não é usado para a lógica internamente.
Então, depois de terminar com todos os tipos, temos chamadas de contrato cruzado usando uma interface de alto nível.
Existem dois deles. A maneira como funciona é que você tem uma macro de near_bindgen chamada ext_contract (representando contrato externo). Você pode dar a ele um nome curto que ele mesmo gerará o qual você poderá usar. Então você tem uma descrição de característica descrevendo a interface do contrato externo que você deseja usar. Isso descreve o fato de que você pode chamar um método de votação em um contrato remoto e passar um argumento.
O argumento is_vote que é um booleano verdadeiro ou falso. Agora você poderá criar uma promessa quando precisar e passar um argumento posicional em vez de um argumento de serialização JSON. A macro se transformará em apis de promessa de baixo nível atrás dos bastidores. A segunda interface é para um retorno de chamada em nosso self, isso é bastante comum, você pode chamá-lo de ext_self. Quando você precisa fazer um retorno de chamada e fazer algo no resultado da promessa assíncrona, você pode ter esse tipo de interface. O que fazemos então é verificar se a ação de staking foi bem-sucedida. Finalmente, temos este corpo de implem
Contract File Structure (Estrutura do Arquivo do Contrato)
Este contrato é dividido em vários módulos.
Você tem o libs.rs que é a entrada principal, e ademais um módulo interno.O módulo interno tem a implementação sem a macro near_bindgen, por tanto, nenhum desses métodos ficará visível para ser chamado por um contrato por outra pessoa dessa chain. Eles só podem ser chamados dentro deste contrato internamente para que não gerem formatos JSON e não desserializem o estado. Todos eles agem como métodos regulares de Rust. Como este contrato funciona de alto nível, quando uma epoch passa, você pode adquirir certas recompensas como validador.
Important Methods of the Contract (Métodos Importantes do Contrato)
Temos um método de ping que atua no contrato. O método ping verifica se um epoch passou e então precisamos distribuir recompensas. Se o epoch mudar, o ping também será reiniciado, porque pode haver alguma mudança no valor da participação total que o contrato deve apostar. O próximo é o depósito.
O método de depósito é o Playable (pagável), o que significa que pode aceitar um depósito anexado. Isso é semelhante ao Ethereum Decorator que permite que você receba fundos somente para os respectivos métodos que os esperam. Portanto, near_bindgen, por padrão, entrará em pane se você tentar chamar um método, por exemplo, ping, e anexar um depósito a esse método. Consequentemente, o pagável nos permite anexar depósitos. Em todos os métodos há um ping interno para garantir que distribuímos as recompensas anteriores antes de alterar qualquer argumento. A estrutura comum é que, se precisarmos refazer, primeiro fazemos algo de logic e depois um restake.
O seguinte método é o deposit_and_stake. Esta é uma combinação entre dois métodos. Primeiro, você deposita o saldo no saldo da sua conta e imediatamente também faz o stake no mesmo valor, em vez de fazer duas transações. Ademais, também é pagável porque também aceita um depósito.
A próxima ação é fazer um withdraw_all (retirar tudo). Essa ordem tenta retirar todo o saldo unstake da conta chamada. Quando você interage com o staking pool, você precisa interagir com a conta que possui o saldo. Neste caso, este é o predecessor_account_id e basicamente verificamos a conta e, em seguida, retiramos o valor que não está bloqueado (unstaked amount), se é possível. Se não for retirado, vai entrar em pane. Por exemplo, se ainda estiver bloqueado devido a desmarcação há menos de 4 epochs.
Withdraw só permite um saque parcial do saldo.
É aí que o Stake_all faz um stake de todo o saldo (stake_all) que está desbloqueado (unstaked) e é muito fora do comum usar esse método, porque você normalmente usa o stake_deposit que te dá imediatamente o saldo do stake.
Então, no stake method, você faz apenas um stake com uma quantia do stake balance. A carteira Moonlight usa um custo separado para depositar em stake, mas eles usam uma transação em lote para fazer isso.
E por último, você tem um unstake_all, que basicamente desmarca todas as suas ações, convertendo-as em um yocto NEAR. Existe um método auxiliar que diz converter meu número de ações em uma quantidade de yocto NEAR e arredondar para baixo, porque não podemos dar a você um valor extra pela sua ação multiplicada pelo preço. É assim que obtemos o valor e, em seguida, chamamos o unstake para o valor fornecido.
O argumento staked_amount_from_num_shares_rounded_down usa o u256, porque os saldos operam em u128. Para evitar desbordes, multiplicamos o total_staked_balance pelo número de ações em u256. O preço é o quociente arredondado para baixo.
A versão de arredondamento para cima staked_amount_from_num_shares_rounded_up é muito semelhante, exceto que fazemos uma verificação que nos permite arredondar para cima e não para baixo. No final de ambos nós o lançamos de volta para u128. Então temos uma ação unstake que é muito semelhante ao unstake_all, exceto que você passa o valor.
Métodos Getter/View
Depois disso, há vários métodos getter que são chamadas de visualização que retornam alguns valores. Você pode obter o saldo que não está em stake da conta, saldo em stake da conta, saldo total da conta, verificar se você pode sacar, saldo o total do stake, que é o valor total que o conjunto de stakes tem no stake ativo.
Então você pode saber quem é o proprietário do staking pool, pode obter a taxa de recompensa atual ou a comissão do staking pool, obter a chave de staking atual e há um ponto separado que verifica se o proprietário pausou o staking pool.
Digamos que o proprietário faça uma migração no staking pool dentro do node. Eles precisam desfazer completamente o staking, então, por exemplo, eles podem pausar o staking pool que enviará uma transação de estado para o protocolo NEAR e, em seguida, não serão reiniciados até que eles retomem o staking pool. No entanto, você ainda pode sacar seus saldos, mas deixará de adquirir recompensas após a aprovação.
Finalmente, você pode obter uma conta legível por humanos que fornece quantos tokens você realmente possui para o número de ações no preço atual e, finalmente, informa se você pode retirar ou não.
Em seguida, você receberá o número de contas que é o número de delegantes para esse staking pool, e também poderá recuperar vários delegados ao mesmo tempo.
Esta é a paginação em um grande número de contas dentro do mapa não ordenado. Uma maneira de fazer isso é usar o auxiliar que chamamos de keys_as a_vector do mapa não ordenado. Ele fornece uma coleção persistente de chaves do mapa e, em seguida, você pode usar um iterador para solicitar as contas dessas chaves. Essa não é a maneira mais eficiente, mas permite implementar a paginação em mapas não ordenados.
Owner Methods (Métodos de proprietário)
Existem vários métodos de proprietário. Um método proprietário é um método que só pode ser chamado pelo proprietário. O proprietário pode atualizar a chave de staking. Digamos que haja um node diferente e o proprietário precise usar uma chave diferente. Todos esses métodos primeiro verificam se somente o proprietário poderia chamá-lo.
Este é o método que altera a comissão do staking pool. O proprietário pode alterar a comissão que estará ativa neste epoch imediatamente a partir deste epoch, mas todas as comissões anteriores serão calculadas usando a taxa anterior.
Portanto, esse foi o método de votação que nos permitiu fazer a transição para a fase dois da rede principal.
Seguidamente estão os dois métodos, já descritos por mim, que permitem pausar o staking e reativar o staking.
O resto são apenas testes. A maior parte do processo está acontecendo internamente.
Simulation Test (Teste simulado)
Também temos basicamente testes simulados para um determinado pool. Esta simulação é como a rede realmente vai funcionar. Primeiro inicializamos o pool.
Bob é o delegante. Bob chama o método de depósito de pool que é o deposit_amount usando o deposit_method. Então Bob pode verificar se o saldo fora do stake está funcionando corretamente. Em seguida Bob introduz a quantia no stake. Então verificamos o valor atual do stake. Verificamos que seja a mesma quantia.
Bob inicia o método ping. Não há recompensas, mas nas simulações as recompensas não estão funcionando de qualquer maneira, neste caso você precisa fazer isso manualmente. Verificaremos mais uma vez que o valor de Bob ainda seja o mesmo. Em seguida o stake pool recomeça. Nós verificamos que o pool tenha recomeçado, e em seguida bloqueia a zero. Então simulamos que o pool adquiriu algumas recompensas (1 NEAR) e Bob dá um ping na piscina. Em seguida, verificamos se o valor que Bob recebeu é positivo. Esse é um caso de simulação muito simples que diz que Bob primeiro depositou no pool, que é o que verifica se a pausa e a retomada funcionam, ou simula que funciona e garante que o pool não entre no stake enquanto estiver em pausa. Então, quando retomado, o pool realmente faz o stake. E portanto, este teste verifica não apenas o mencionado, mas também se Bob adquiriu a recompensa e a distribuiu. Há outro teste que verifica algo do processo lógico, só que é algo mais complicado. Existem alguns testes de unidade na parte inferior disto, que devem verificar certas coisas.
Alguns desses testes não são os ideais, porém verificam certos detalhes que tenham sido bons o suficiente para garantir que o cálculo fosse válido.
internal.rs
Internal Ping Method
Vamos passar para o internal_ping. Este é o método que qualquer pessoa pode chamar por meio de ping para garantir que as recompensas sejam distribuídas. Neste momento, temos pools de staking ativos e há uma conta patrocinada por uma das pessoas do NEAR que basicamente faz ping a todos os stake pools cada 15 minutos com a missão de garantir que cada um deles tenha distribuído as recompensas para serem exibidas no seu respectivo saldo. Dessa forma, a distribuição de recompensas funciona. Primeiro verificamos a altura do epoch atual, portanto, se a altura do epoch for a mesma, significa que a epoch não mudou, devolvemos false para que você não precise fazer um restake.
Agora, se o epoch mudou, lembramos que o epoch atual (epoch height) existe, e obtemos o novo saldo total da conta. O ping pode ser chamado quando alguns tokens foram depositados por meio de cédulas de depósito, e eles já fazem parte do account_balance, e como o ping foi chamado antes, precisamos subtrair esse saldo antes de distribuir as recompensas.. Obtemos o valor total que a conta possui, incluindo ambos saldos, o bloqueado e o desbloqueado. O saldo bloqueado é um valor de stake que adquire recompensas, e o saldo desbloqueado também pode ter recompensas em certos cenários em que você diminui o seu stake, mas suas recompensas ainda serão refletidas nos próximos dois epochs. Logo após isso, eles chegarão ao valor fora do stake. Verificamos usando assert, que o saldo total é maior do que o saldo total anterior. Essa é uma variante que o staking pool requer. Houve uma grande quantidade de processos na rede de teste que falharam nessa invariável porque as pessoas ainda tinham chaves de acesso no mesmo staking pool e, quando você as tem, gasta o saldo em “gas” e pode diminuir seu saldo total sem adquirir a recompensa. Por fim, calculamos a quantidade de recompensas que o staking pool recebeu.Este é o saldo total menos o saldo total conhecido anterior, o saldo do epoch anterior. Se as recompensas forem positivas, nós as distribuímos e a primeira coisa que fazemos é calcular a recompensa que o proprietário recebe como comissão.
Multiplicamos a reward_fee_fraction pela recompensa total recebida e isso é arredondado para baixo com o numerador em u256 multiplicado pelo valor dividido pelo denominador em u256.
O owner_fee é o valor em yocto NEAR que o proprietário irá manter para si. O remaining_reward são as recompensas restantes que precisam ser restaked. Em seguida, passa a ser restaked. O proprietário recebeu as recompensas em yocto NEAR, não em shares, mas porque toda a lógica tem que estar em compartilhamentos, o proprietário do staking pool compra ações ao preço das distribuições pós-recompensa para o restante dos delegantes. Portanto, num_shares é o número de shares que o proprietário receberá como compensação pela execução do staking pool. Se for positivo, aumentamos a quantidade de ações e salvamos a conta do proprietário e também aumentamos a quantidade total de participação em ações. Se por algum motivo, durante o rounding down, esse saldo se tornasse zero, a recompensa venha ser muito pequena e o preço por ação era muito grande e o pool recebia apenas zero recompensas. Nesse caso, esse saldo será enviado apenas para o preço por ação em vez de compensar o proprietário. Em seguida, colocamos alguns totais do logging data que indicam que o epoch atual existe, que recebemos as recompensas em uma quantidade de ações ou tokens de staking, que o saldo total de staking do pool é algo e registramos o número de shares. A única maneira de expor o número de shares ao mundo externo é através dos logs. Finalmente, iremos lembrar apenas do novo saldo total e pronto. Distribuímos todas as recompensas em tempo constante e atualizamos apenas uma conta (conta do proprietário) para a comissão, e somente se a comissão fosse positiva.
Internal Stake Method (Método Interno de Stake)
O internal_stake é onde implementamos o fundo de garantia de preço. Digamos que o predecessor, a quem, neste caso chamaremos account_id, quer pôr em stake uma quantidade de tokens.
O saldo na verdade não é um tipo JSON, porque é um método interno, portanto não precisamos de JSON aqui. Calculamos quantas ações são arredondadas (para baixo) que são necessárias para o valor determinado a ser posto em stake, será quantas ações o proprietário receberá. Tem que ser positivo.
Então verificamos o valor, novamente arredondado para baixo, que o proprietário deve pagar pelas ações. Isso é para garantir que quando o proprietário comprou ações e as converteu de volta sem recompensas nunca perdeu o 1 yocto NEAR, pois poderia romper a garantia. finalmente, confirmamos que a conta tem o suficiente para pagar o valor cobrado, e diminuímos o saldo interno que não está em stake, e aumentamos o saldo interno do número de ações da conta. Em seguida, redondeamos o staked_amount_from_num_shares_rounded_up para que o número de ações seja realmente arredondado. Este 1 centavo extra ou 1 yocto NEAR extra, virá do fundo garantia durante o arredondamento das cotas. Cobramos menos do usuário, mas contribuímos mais com o valor desse 1 trilhão de yocto NEAR que havíamos designado originalmente para isso. Essa diferença geralmente é de apenas 1 yocto NEAR que pode vir do que foi arredondado para cima ou para baixo. Depois disso, há a quantidade de total_stake_balance e total_stake_shares. Seguidamente, cunhamos novas ações com eles. E por fim, colocamos um log e retornamos o resultado.
O unstake funciona de maneira muito semelhante. Você arredonda para a quantidade de ações que precisa pagar. Em seguida, calculamos o valor que você recebe, arredondando novamente para pagar a mais por isso. Isso também vem de um fundo de garantia. Em seguida, diminuímos o valor das ações para aumentar a quantidade e indicamos quando você pode desbloquear o saldo num período de quatro epochs. O untake_amount é arredondado para baixo para que o unstake seja um pouco menos para garantir o preço dos outros participantes do pool. É basicamente assim que o staking pool funciona e como a matemática funciona. Compensamos os erros de arredondamento dos fundos que alocamos.
Conclusão
Atualizamos as chaves ristretto durante a elaboração deste contrato e foi uma surpresa que fosse necessário tomar isso em conta. No STAKE_SHARE_PRICE_GUARANTEE_FUND 1 trilhão de yocto NEAR deve ser suficiente para 500 bilhões de transações que devem ser longas o suficiente para o staking pool para que não possa ser recarregado porque as recompensas serão imediatamente redistribuídas para o total_stake_balance no próximo ping. Gastamos bastante tempo e esforço neste contrato, porque fizemos muitas análises de segurança, incluindo internas e externas, especialmente em torno desses cálculos. Foi complicado, e descobrimos algumas coisas, como, por exemplo, a tecla ristretto que apareceu durante as revisões. Nós marcamos o log de alterações deste contrato, também no readme há um monte de coisas que surgiram durante o desenvolvimento e testes no sistema ao vivo, mas a versão original tardou aproximadamente uma semana para ser escrita. Mais tarde, demos uma boa polida, testamos e implementamos várias melhorias. Então fizemos um montão de revisões. Tipo, pausar e reativar foi solicitado pelo pool, porque, caso contrário, o proprietário não teria a capacidade de desfazer o unstake se o seu node falhasse. Eles estarão atacando a rede. Essencialmente, essa participação ativa estaria solicitando a validação e não executando a rede. Antes não tínhamos essa contundência. Isso, não só era um problema para os participantes, mas também para a própria rede. Dessa forma, o proprietário pode pausar o staking se não quiser executar o pool que migra para o pool e comunicar-se o máximo possível antes disso. Em seguida, atualizamos a interface de votação para corresponder ao final da fase 2 do contrato de votação. Adicionamos métodos de visualização auxiliares para poder consultar contas de maneira legível por humanos. Por fim, houve algumas melhorias nos métodos de agrupamento em lote, então o deposit_and_stake, stake_all, untake_all e o retire_all, em vez de ter que fazer uma chamada de visualização primeiro, obter o valor e colocar o valor para chamar o stake. Aqui está a maneira como corrigimos isso.
Quando você faz o stake, não é apenas com o valor, mas também anexamos um compromisso de verificar se o stake deu certo. Isto é necessário para dois motivos: se você estiver tentando fazer o stake com uma chave inválida (não uma chave específica de ristretto), o compromisso falhará antes da execução. Ele falhará na validação antes de enviá-lo, e isso fará com que você não precise verificá-lo no contrato. Ele reverterá a última chamada e tudo ficará bem. Ademais, introduzimos o minimum stake no nível do protocolo. O mínimo stake possível é o equivalente a um décimo do valor do último preço do assento, e se o seu contrato tentar fazer o stake com menos que isso, a ação falhará e você não enviará a promessa.Digamos que você queira desfazer uma aposta e deixou cair seu saldo abaixo de um décimo do valor.A ação de staking pode falhar e você não destake, enquanto você precisa para garantir que o unstake aconteça. Nesse caso, temos esse call back que verifica se o stake tenha sido completado e bem sucedido. Esse call back basicamente verifica se funcionou, se falhar, e o saldo for positivo, precisamos desfazer o stake. Portanto, ele chamará o unstake para uma ação em que o valor dele é zero para garantir que todo o saldo seja liberado. Foi possível retirar em 4 epochs durante os testes desses contratos que fizemos na testnet beta 9 antes da manutenção. O contrato estava pronto, talvez pela volta do horário de verão, por isso o teste desta iteração levou provavelmente de 2 a 4 meses devido à complexidade que envolve a interação com o protocolo. Uma coisa que seria muito boa seria ter um stake ou depósito e fazer um stake_all em um contrato de lock-up. No momento, você precisa emitir manualmente quanto deseja pôr no stake em um contrato lock-up, mas seria ótimo se você não precisasse pensar no seu yocto NEAR e no quanto ele está bloqueado para ser guardado. Você só quer fazer o stake all do seu lock up, mas como já foi implantado, já é tarde demais para pensar nisso. Há também algum gás que é codificado e, com a diminuição habitual da taxa, esses números não podem ser alterados porque já estão na chain.
Portanto, “vote” não é importante, mas o método ON_STAKE_ACTION_GAS exige que você tenha um número grande para cada stake e não pode diminuí-lo. A tomada de ações de risco em todas as chamadas deste contrato exigirá que tenhamos uma grande quantidade de gás e o problema é que isso é um verdadeiro desperdício. Digamos que concordamos em queimar todo o gás, esse gás será queimado sempre e logicamente desperdiçado, além de limitar o número de transações que você pode colocar em um bloco se estivermos restringindo o gás com base nesse caso. sucederam muitas iterações no teste do contrato usando a estrutura de simulação de teste que melhoramos muito. Se chegarmos aos contratos Lockup, eventualmente você poderá ver como a estrutura desses contratos melhorou em relação a este.