Skip to content

Commit

Permalink
Merge pull request #3385 from ElrondNetwork/liquid-staking-unit-tests
Browse files Browse the repository at this point in the history
Liquid staking unit and integration tests
  • Loading branch information
sasurobert authored Aug 31, 2021
2 parents 337b911 + 4d7d336 commit 6ab9cd5
Show file tree
Hide file tree
Showing 13 changed files with 1,456 additions and 44 deletions.
68 changes: 68 additions & 0 deletions integrationTests/testProcessorNode.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"encoding/hex"
"fmt"
"math"
"math/big"
"strconv"
"sync"
Expand Down Expand Up @@ -1779,6 +1780,73 @@ func (tpn *TestProcessorNode) InitDelegationManager() {
log.LogIfError(err)
}

// InitLiquidStaking will initialize the liquid staking contract whenever required
func (tpn *TestProcessorNode) InitLiquidStaking() []byte {
if tpn.ShardCoordinator.SelfId() != core.MetachainShardId {
return nil
}

vmInput := &vmcommon.ContractCallInput{
VMInput: vmcommon.VMInput{
CallerAddr: vm.ESDTSCAddress,
CallValue: big.NewInt(0),
Arguments: [][]byte{},
GasProvided: math.MaxUint64,
},
RecipientAddr: vm.ESDTSCAddress,
Function: "initDelegationESDTOnMeta",
}

systemVM, err := tpn.VMContainer.Get(factory.SystemVirtualMachine)
log.LogIfError(err)

vmOutput, err := systemVM.RunSmartContractCall(vmInput)
log.LogIfError(err)
if vmOutput.ReturnCode != vmcommon.Ok {
log.Error("error while initializing system SC", "return code", vmOutput.ReturnCode)
}

err = tpn.processSCOutputAccounts(vmOutput)
log.LogIfError(err)

_, err = tpn.AccntState.Commit()
log.LogIfError(err)

codeMetaData := &vmcommon.CodeMetadata{
Upgradeable: false,
Payable: false,
Readable: true,
}

tokenID := vmOutput.ReturnData[0]
vmInputCreate := &vmcommon.ContractCreateInput{
VMInput: vmcommon.VMInput{
CallerAddr: vm.LiquidStakingSCAddress,
Arguments: [][]byte{tokenID},
CallValue: zero,
},
ContractCode: vm.DelegationManagerSCAddress,
ContractCodeMetadata: codeMetaData.ToBytes(),
}

vmOutput, err = systemVM.RunSmartContractCreate(vmInputCreate)
log.LogIfError(err)
if vmOutput.ReturnCode != vmcommon.Ok {
log.Error("error while initializing system SC", "return code", vmOutput.ReturnCode)
}

err = tpn.processSCOutputAccounts(vmOutput)
log.LogIfError(err)

err = tpn.updateSystemSCContractsCode(vmInputCreate.ContractCodeMetadata, vm.LiquidStakingSCAddress)
log.LogIfError(err)

_, err = tpn.AccntState.Commit()
log.LogIfError(err)

return tokenID
}

func (tpn *TestProcessorNode) updateSystemSCContractsCode(contractMetadata []byte, scAddress []byte) error {
userAcc, err := tpn.getUserAccount(scAddress)
if err != nil {
Expand Down
190 changes: 190 additions & 0 deletions integrationTests/vm/delegation/liquidStaking_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,190 @@
// +build !race

package delegation

import (
"bytes"
"math/big"
"testing"
"time"

"github.com/ElrondNetwork/elrond-go-core/core"
logger "github.com/ElrondNetwork/elrond-go-logger"
"github.com/ElrondNetwork/elrond-go/integrationTests"
"github.com/ElrondNetwork/elrond-go/integrationTests/vm/esdt"
"github.com/ElrondNetwork/elrond-go/testscommon/txDataBuilder"
"github.com/ElrondNetwork/elrond-go/vm"
vmcommon "github.com/ElrondNetwork/elrond-vm-common"
"github.com/stretchr/testify/require"
)

var log = logger.GetOrCreate("liquidStaking")

func TestDelegationSystemSCWithLiquidStaking(t *testing.T) {
if testing.Short() {
t.Skip("this is not a short test")
}

nodes, idxProposers, delegationAddress, tokenID, nonce, round := setupNodesDelegationContractInitLiquidStaking(t)
defer func() {
for _, n := range nodes {
_ = n.Messenger.Close()
}
}()

txData := txDataBuilder.NewBuilder().Clear().
Func("claimDelegatedPosition").
Bytes(big.NewInt(1).Bytes()).
Bytes(delegationAddress).
Bytes(big.NewInt(5000).Bytes()).
ToString()
for _, node := range nodes {
integrationTests.CreateAndSendTransaction(node, nodes, big.NewInt(0), vm.LiquidStakingSCAddress, txData, core.MinMetaTxExtraGasCost)
}

nrRoundsToPropagateMultiShard := 12
time.Sleep(time.Second)
nonce, round = integrationTests.WaitOperationToBeDone(t, nodes, nrRoundsToPropagateMultiShard, nonce, round, idxProposers)
time.Sleep(time.Second)

// claim again
for _, node := range nodes {
integrationTests.CreateAndSendTransaction(node, nodes, big.NewInt(0), vm.LiquidStakingSCAddress, txData, core.MinMetaTxExtraGasCost)
}

time.Sleep(time.Second)
nonce, round = integrationTests.WaitOperationToBeDone(t, nodes, nrRoundsToPropagateMultiShard, nonce, round, idxProposers)
time.Sleep(time.Second)

for i := 1; i < len(nodes); i++ {
checkLPPosition(t, nodes[i].OwnAccount.Address, nodes, tokenID, uint64(1), big.NewInt(10000))
}
// owner is not allowed to get LP position
checkLPPosition(t, nodes[0].OwnAccount.Address, nodes, tokenID, uint64(1), big.NewInt(0))
metaNode := getNodeWithShardID(nodes, core.MetachainShardId)
allDelegatorAddresses := make([][]byte, 0)
for i := 1; i < len(nodes); i++ {
allDelegatorAddresses = append(allDelegatorAddresses, nodes[i].OwnAccount.Address)
}
verifyDelegatorIsDeleted(t, metaNode, allDelegatorAddresses, delegationAddress)

oneTransfer := &vmcommon.ESDTTransfer{
ESDTValue: big.NewInt(1000),
ESDTTokenName: tokenID,
ESDTTokenType: uint32(core.NonFungible),
ESDTTokenNonce: 1,
}
esdtTransfers := []*vmcommon.ESDTTransfer{oneTransfer, oneTransfer, oneTransfer, oneTransfer, oneTransfer}
txBuilder := txDataBuilder.NewBuilder().MultiTransferESDTNFT(vm.LiquidStakingSCAddress, esdtTransfers)
txBuilder.Bytes([]byte("unDelegatePosition"))
for _, node := range nodes {
integrationTests.CreateAndSendTransaction(node, nodes, big.NewInt(0), node.OwnAccount.Address, txBuilder.ToString(), core.MinMetaTxExtraGasCost)
}

txBuilder = txDataBuilder.NewBuilder().MultiTransferESDTNFT(vm.LiquidStakingSCAddress, esdtTransfers)
txBuilder.Bytes([]byte("returnPosition"))
for _, node := range nodes {
integrationTests.CreateAndSendTransaction(node, nodes, big.NewInt(0), node.OwnAccount.Address, txBuilder.ToString(), core.MinMetaTxExtraGasCost)
}
time.Sleep(time.Second)
finalWait := 20
nonce, round = integrationTests.WaitOperationToBeDone(t, nodes, finalWait, nonce, round, idxProposers)
time.Sleep(time.Second)

for _, node := range nodes {
checkLPPosition(t, node.OwnAccount.Address, nodes, tokenID, uint64(1), big.NewInt(0))
}

verifyDelegatorsStake(t, metaNode, "getUserActiveStake", allDelegatorAddresses, delegationAddress, big.NewInt(5000))
verifyDelegatorsStake(t, metaNode, "getUserUnStakedValue", allDelegatorAddresses, delegationAddress, big.NewInt(5000))
}

func setupNodesDelegationContractInitLiquidStaking(
t *testing.T,
) ([]*integrationTests.TestProcessorNode, []int, []byte, []byte, uint64, uint64) {
numOfShards := 2
nodesPerShard := 2
numMetachainNodes := 2

nodes := integrationTests.CreateNodes(
numOfShards,
nodesPerShard,
numMetachainNodes,
)

integrationTests.DisplayAndStartNodes(nodes)

idxProposers := make([]int, numOfShards+1)
for i := 0; i < numOfShards; i++ {
idxProposers[i] = i * nodesPerShard
}
idxProposers[numOfShards] = numOfShards * nodesPerShard

tokenID := initDelegationManagementAndLiquidStaking(nodes)

initialVal := big.NewInt(10000000000)
initialVal.Mul(initialVal, initialVal)
integrationTests.MintAllNodes(nodes, initialVal)

delegationAddress := createNewDelegationSystemSC(nodes[0], nodes)

round := uint64(0)
nonce := uint64(0)
round = integrationTests.IncrementAndPrintRound(round)
nonce++

time.Sleep(time.Second)
nrRoundsToPropagateMultiShard := 6
nonce, round = integrationTests.WaitOperationToBeDone(t, nodes, nrRoundsToPropagateMultiShard, nonce, round, idxProposers)
time.Sleep(time.Second)

txData := "delegate"
for _, node := range nodes {
integrationTests.CreateAndSendTransaction(node, nodes, big.NewInt(10000), delegationAddress, txData, core.MinMetaTxExtraGasCost)
}

time.Sleep(time.Second)
nonce, round = integrationTests.WaitOperationToBeDone(t, nodes, nrRoundsToPropagateMultiShard, nonce, round, idxProposers)
time.Sleep(time.Second)

return nodes, idxProposers, delegationAddress, tokenID, nonce, round
}

func initDelegationManagementAndLiquidStaking(nodes []*integrationTests.TestProcessorNode) []byte {
var tokenID []byte
for _, node := range nodes {
node.InitDelegationManager()
tmpTokenID := node.InitLiquidStaking()
if len(tmpTokenID) != 0 {
if len(tokenID) == 0 {
tokenID = tmpTokenID
}

if !bytes.Equal(tokenID, tmpTokenID) {
log.Error("tokenID missmatch", "current", tmpTokenID, "old", tokenID)
}
}
}
return tokenID
}

func checkLPPosition(
t *testing.T,
address []byte,
nodes []*integrationTests.TestProcessorNode,
tokenID []byte,
nonce uint64,
value *big.Int,
) {
tokenIdentifierPlusNonce := append(tokenID, big.NewInt(0).SetUint64(nonce).Bytes()...)
esdtData := esdt.GetESDTTokenData(t, address, nodes, string(tokenIdentifierPlusNonce))

if value.Cmp(big.NewInt(0)) == 0 {
require.Nil(t, esdtData.TokenMetaData)
return
}

require.NotNil(t, esdtData.TokenMetaData)
require.Equal(t, vm.LiquidStakingSCAddress, esdtData.TokenMetaData.Creator)
require.Equal(t, value.Bytes(), esdtData.Value.Bytes())
}
12 changes: 11 additions & 1 deletion testscommon/txDataBuilder/builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import (
"math/big"

"github.com/ElrondNetwork/elrond-go-core/core"
vmcommon "github.com/ElrondNetwork/elrond-vm-common"
)

// txDataBuilder constructs a string to be used for transaction arguments
Expand Down Expand Up @@ -147,11 +148,20 @@ func (builder *txDataBuilder) TransferESDT(token string, value int64) *txDataBui
return builder.Func(core.BuiltInFunctionESDTTransfer).Str(token).Int64(value)
}

//TransferESDTNFT appends to the data string all the elements required to request an ESDT NFT transfer.
// TransferESDTNFT appends to the data string all the elements required to request an ESDT NFT transfer.
func (builder *txDataBuilder) TransferESDTNFT(token string, nonce int, value int64) *txDataBuilder {
return builder.Func(core.BuiltInFunctionESDTNFTTransfer).Str(token).Int(nonce).Int64(value)
}

// MultiTransferESDTNFT appends to the data string all the elements required to request an Multi ESDT NFT transfer.
func (builder *txDataBuilder) MultiTransferESDTNFT(destinationAddress []byte, transfers []*vmcommon.ESDTTransfer) *txDataBuilder {
txBuilder := builder.Func(core.BuiltInFunctionMultiESDTNFTTransfer).Bytes(destinationAddress).Int(len(transfers))
for _, transfer := range transfers {
txBuilder.Bytes(transfer.ESDTTokenName).Int(int(transfer.ESDTTokenNonce)).BigInt(transfer.ESDTValue)
}
return txBuilder
}

// BurnESDT appends to the data string all the elements required to burn ESDT tokens.
func (builder *txDataBuilder) BurnESDT(token string, value int64) *txDataBuilder {
return builder.Func(core.BuiltInFunctionESDTBurn).Str(token).Int64(value)
Expand Down
3 changes: 3 additions & 0 deletions vm/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -253,3 +253,6 @@ var ErrInvalidDelegationTicker = errors.New("invalid delegation ticker name")

// ErrInvalidReturnData signals that invalid return data was provided
var ErrInvalidReturnData = errors.New("invalid return data")

// ErrNotEnoughRemainingFunds signals that operation is invalid as remaining funds are below minimum
var ErrNotEnoughRemainingFunds = errors.New("not enough remaining funds - do not leave dust behind")
2 changes: 0 additions & 2 deletions vm/factory/systemSCFactory.go
Original file line number Diff line number Diff line change
Expand Up @@ -294,13 +294,11 @@ func (scf *systemSCFactory) createDelegationManagerContract() (vm.SystemSmartCon
func (scf *systemSCFactory) createLiquidStakingContract() (vm.SystemSmartContract, error) {
argsLiquidStaking := systemSmartContracts.ArgsNewLiquidStaking{
Eei: scf.systemEI,
DelegationMgrSCAddress: vm.DelegationManagerSCAddress,
LiquidStakingSCAddress: vm.LiquidStakingSCAddress,
GasCost: scf.gasCost,
Marshalizer: scf.marshalizer,
Hasher: scf.hasher,
EpochNotifier: scf.epochNotifier,
EndOfEpochAddress: vm.EndOfEpochAddress,
EpochConfig: *scf.epochConfig,
}
liquidStaking, err := systemSmartContracts.NewLiquidStakingSystemSC(argsLiquidStaking)
Expand Down
Loading

0 comments on commit 6ab9cd5

Please sign in to comment.