diff --git a/pkg/core/native/contract.go b/pkg/core/native/contract.go index a953827eb..133f72c95 100644 --- a/pkg/core/native/contract.go +++ b/pkg/core/native/contract.go @@ -75,7 +75,7 @@ func NewContracts(cfg config.ProtocolConfiguration) *Contracts { cs.Ledger = ledger cs.Contracts = append(cs.Contracts, ledger) - gas := newGAS(int64(cfg.InitialGASSupply)) + gas := newGAS(int64(cfg.InitialGASSupply), cfg.P2PSigExtensions) neo := newNEO() neo.GAS = gas gas.NEO = neo diff --git a/pkg/core/native/native_gas.go b/pkg/core/native/native_gas.go index e06925741..1a7784fd1 100644 --- a/pkg/core/native/native_gas.go +++ b/pkg/core/native/native_gas.go @@ -8,6 +8,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/native/nativenames" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" @@ -18,7 +19,8 @@ type GAS struct { nep17TokenNative NEO *NEO - initialSupply int64 + initialSupply int64 + p2pSigExtensionsEnabled bool } const gasContractID = -6 @@ -27,8 +29,11 @@ const gasContractID = -6 const GASFactor = NEOTotalSupply // newGAS returns GAS native contract. -func newGAS(init int64) *GAS { - g := &GAS{initialSupply: init} +func newGAS(init int64, p2pSigExtensionsEnabled bool) *GAS { + g := &GAS{ + initialSupply: init, + p2pSigExtensionsEnabled: p2pSigExtensionsEnabled, + } defer g.UpdateHash() nep17 := newNEP17Native(nativenames.Gas, gasContractID) @@ -105,6 +110,15 @@ func (g *GAS) OnPersist(ic *interop.Context) error { var netFee int64 for _, tx := range ic.Block.Transactions { netFee += tx.NetworkFee + if g.p2pSigExtensionsEnabled { + // Reward for NotaryAssisted attribute will be minted to designated notary nodes + // by Notary contract. + attrs := tx.GetAttributes(transaction.NotaryAssistedT) + if len(attrs) != 0 { + na := attrs[0].Value.(*transaction.NotaryAssisted) + netFee -= (int64(na.NKeys) + 1) * transaction.NotaryServiceFeePerKey + } + } } g.mint(ic, primary, big.NewInt(int64(netFee)), false) return nil diff --git a/pkg/core/native_gas_test.go b/pkg/core/native_gas_test.go index 6c6afdfc4..34c57caed 100644 --- a/pkg/core/native_gas_test.go +++ b/pkg/core/native_gas_test.go @@ -4,10 +4,17 @@ import ( "math/big" "testing" + "github.com/nspcc-dev/neo-go/internal/testchain" + "github.com/nspcc-dev/neo-go/pkg/core/interop" + "github.com/nspcc-dev/neo-go/pkg/core/native/noderoles" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm" + "github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" + "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) @@ -52,3 +59,91 @@ func TestGAS_Roundtrip(t *testing.T) { require.Equal(t, bc.BlockHeight(), updatedHeight) }) } + +func TestGAS_RewardWithP2PSigExtensionsEnabled(t *testing.T) { + chain := newTestChain(t) + notaryHash := chain.contracts.Notary.Hash + gasHash := chain.contracts.GAS.Hash + signer := testchain.MultisigScriptHash() + var err error + + const ( + nNotaries = 2 + nKeys = 4 + ) + + // set Notary nodes and check their balance + notaryNodes := make([]*keys.PrivateKey, nNotaries) + notaryNodesPublicKeys := make(keys.PublicKeys, nNotaries) + for i := range notaryNodes { + notaryNodes[i], err = keys.NewPrivateKey() + require.NoError(t, err) + notaryNodesPublicKeys[i] = notaryNodes[i].PublicKey() + } + chain.setNodesByRole(t, true, noderoles.P2PNotary, notaryNodesPublicKeys) + for _, notaryNode := range notaryNodesPublicKeys { + checkBalanceOf(t, chain, notaryNode.GetScriptHash(), 0) + } + // deposit GAS for `signer` with lock until the next block + depositAmount := 100_0000 + (2+int64(nKeys))*transaction.NotaryServiceFeePerKey // sysfee + netfee of the next transaction + transferTx := transferTokenFromMultisigAccount(t, chain, notaryHash, gasHash, depositAmount, signer, int64(chain.BlockHeight()+1)) + res, err := chain.GetAppExecResults(transferTx.Hash(), trigger.Application) + require.NoError(t, err) + require.Equal(t, vm.HaltState, res[0].VMState) + require.Equal(t, 0, len(res[0].Stack)) + + // save initial validators balance + balances := make(map[int]int64, testchain.ValidatorsCount) + for i := 0; i < testchain.ValidatorsCount; i++ { + balances[i] = chain.GetUtilityTokenBalance(testchain.PrivateKeyByID(i).GetScriptHash()).Int64() + } + ic := interop.NewContext(trigger.Application, chain, chain.dao, nil, nil, nil, nil, chain.log) + tsInitial := chain.contracts.GAS.TotalSupply(ic, nil).Value().(*big.Int).Int64() + + // send transaction with Notary contract as a sender + tx := chain.newTestTx(util.Uint160{}, []byte{byte(opcode.PUSH1)}) + tx.Attributes = append(tx.Attributes, transaction.Attribute{Type: transaction.NotaryAssistedT, Value: &transaction.NotaryAssisted{NKeys: uint8(nKeys)}}) + tx.NetworkFee = (2 + int64(nKeys)) * transaction.NotaryServiceFeePerKey + tx.Signers = []transaction.Signer{ + { + Account: notaryHash, + Scopes: transaction.None, + }, + { + Account: signer, + Scopes: transaction.None, + }, + } + tx.Scripts = []transaction.Witness{ + { + InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, notaryNodes[0].SignHashable(uint32(testchain.Network()), tx)...), + }, + { + InvocationScript: testchain.Sign(tx), + VerificationScript: testchain.MultisigVerificationScript(), + }, + } + b := chain.newBlock(tx) + require.NoError(t, chain.AddBlock(b)) + checkBalanceOf(t, chain, notaryHash, int(depositAmount-tx.SystemFee-tx.NetworkFee)) + singleReward := transaction.NotaryServiceFeePerKey * (nKeys + 1) / nNotaries + for _, notaryNode := range notaryNodesPublicKeys { + checkBalanceOf(t, chain, notaryNode.GetScriptHash(), singleReward) + } + for i := 0; i < testchain.ValidatorsCount; i++ { + newBalance := chain.GetUtilityTokenBalance(testchain.PrivateKeyByID(i).GetScriptHash()).Int64() + expectedBalance := balances[i] + if i == int(b.Index)%testchain.CommitteeSize() { + // committee reward + expectedBalance += 5000_0000 + } + if testchain.IDToOrder(i) == int(b.PrimaryIndex) { + // primary reward + expectedBalance += tx.NetworkFee - int64(singleReward*nNotaries) + } + assert.Equal(t, expectedBalance, newBalance, i) + } + tsUpdated := chain.contracts.GAS.TotalSupply(ic, nil).Value().(*big.Int).Int64() + tsExpected := tsInitial + 5000_0000 - tx.SystemFee + require.Equal(t, tsExpected, tsUpdated) +}