forked from cerc-io/ipld-eth-server
fixed tests for getting balanceOf and allowance, added balanceOf and allowance to the repo and repo tests and updated sql schema with new token_balance and token_allowance tables to accept this data
This commit is contained in:
@@ -22,6 +22,7 @@ import (
|
||||
. "github.com/onsi/ginkgo"
|
||||
. "github.com/onsi/gomega"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/vulcanize/vulcanizedb/examples/constants"
|
||||
"github.com/vulcanize/vulcanizedb/examples/erc20_watcher/every_block"
|
||||
"github.com/vulcanize/vulcanizedb/pkg/fakes"
|
||||
@@ -32,7 +33,7 @@ import (
|
||||
)
|
||||
|
||||
var _ = Describe("ERC20 Getter", func() {
|
||||
blockNumber := int64(5502914)
|
||||
blockNumber := int64(6194634)
|
||||
|
||||
Describe("totalSupply", func() {
|
||||
It("gets total supply data from the blockchain with the correct arguments", func() {
|
||||
@@ -64,8 +65,8 @@ var _ = Describe("ERC20 Getter", func() {
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedResult := big.Int{}
|
||||
expectedResult.SetString("27647235749155415536952630", 10)
|
||||
Expect(result).To(Equal(expectedResult))
|
||||
expectedResult.SetString("47327413946297204537985606", 10)
|
||||
Expect(result.String()).To(Equal(expectedResult.String()))
|
||||
})
|
||||
|
||||
It("returns an error if the call to the blockchain fails", func() {
|
||||
@@ -80,4 +81,132 @@ var _ = Describe("ERC20 Getter", func() {
|
||||
Expect(err.Error()).To(ContainSubstring(fakes.FakeError.Error()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("balanceOf", func() {
|
||||
It("gets balance of a token holder address at a token contract address from the blockchain with the correct arguments", func() {
|
||||
fakeBlockChain := fakes.NewMockBlockChain()
|
||||
testGetter := every_block.NewGetter(fakeBlockChain)
|
||||
testAbi := "testAbi"
|
||||
testContractAddress := "testContractAddress"
|
||||
|
||||
testTokenHolderAddress := common.HexToAddress("0x2cccc4b4708b318a6290511aac75d6c3dbe0cf9f")
|
||||
hashArgs := []common.Address{testTokenHolderAddress}
|
||||
balanceOfArgs := make([]interface{}, len(hashArgs))
|
||||
for i, s := range hashArgs {
|
||||
balanceOfArgs[i] = s
|
||||
}
|
||||
|
||||
_, err := testGetter.GetBalance(testAbi, testContractAddress, blockNumber, balanceOfArgs)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedResult := big.Int{}
|
||||
expected := &expectedResult
|
||||
fakeBlockChain.AssertFetchContractDataCalledWith(testAbi, testContractAddress, "balanceOf", balanceOfArgs, &expected, blockNumber)
|
||||
})
|
||||
|
||||
It("gets a token holder address's balance on the dai contract at the given block height", func() {
|
||||
infuraIPC := "https://mainnet.infura.io/J5Vd2fRtGsw0zZ0Ov3BL"
|
||||
rawRpcClient, err := rpc.Dial(infuraIPC)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC)
|
||||
ethClient := ethclient.NewClient(rawRpcClient)
|
||||
blockChainClient := client.NewEthClient(ethClient)
|
||||
node := node.MakeNode(rpcClient)
|
||||
transactionConverter := rpc2.NewRpcTransactionConverter(ethClient)
|
||||
blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter)
|
||||
realGetter := every_block.NewGetter(blockChain)
|
||||
|
||||
testTokenHolderAddress := common.HexToAddress("0x2cccc4b4708b318a6290511aac75d6c3dbe0cf9f")
|
||||
hashArgs := []common.Address{testTokenHolderAddress}
|
||||
balanceOfArgs := make([]interface{}, len(hashArgs))
|
||||
for i, s := range hashArgs {
|
||||
balanceOfArgs[i] = s
|
||||
}
|
||||
|
||||
result, err := realGetter.GetBalance(constants.DaiAbiString, constants.DaiContractAddress, blockNumber, balanceOfArgs)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedResult := big.Int{}
|
||||
expectedResult.SetString("1000000000000000000000000", 10)
|
||||
Expect(result.String()).To(Equal(expectedResult.String()))
|
||||
})
|
||||
|
||||
It("returns an error if the call to the blockchain fails", func() {
|
||||
blockChain := fakes.NewMockBlockChain()
|
||||
blockChain.SetFetchContractDataErr(fakes.FakeError)
|
||||
errorGetter := every_block.NewGetter(blockChain)
|
||||
result, err := errorGetter.GetBalance("", "", 0, nil)
|
||||
|
||||
Expect(result.String()).To(Equal("0"))
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("balanceOf"))
|
||||
Expect(err.Error()).To(ContainSubstring(fakes.FakeError.Error()))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("allowance", func() {
|
||||
It("gets allowance data from the blockchain with the correct arguments", func() {
|
||||
fakeBlockChain := fakes.NewMockBlockChain()
|
||||
testGetter := every_block.NewGetter(fakeBlockChain)
|
||||
testAbi := "testAbi"
|
||||
testContractAddress := "testContractAddress"
|
||||
|
||||
testTokenHolderAddress := common.HexToAddress("0x2cccc4b4708b318a6290511aac75d6c3dbe0cf9f")
|
||||
testTokenSpenderAddress := common.HexToAddress("0x2cccc4b4708b318a6290511aac75d6c3dbe0cf9f")
|
||||
|
||||
hashArgs := []common.Address{testTokenHolderAddress, testTokenSpenderAddress}
|
||||
allowanceArgs := make([]interface{}, len(hashArgs))
|
||||
for i, s := range hashArgs {
|
||||
allowanceArgs[i] = s
|
||||
}
|
||||
|
||||
_, err := testGetter.GetAllowance(testAbi, testContractAddress, blockNumber, allowanceArgs)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedResult := big.Int{}
|
||||
expected := &expectedResult
|
||||
fakeBlockChain.AssertFetchContractDataCalledWith(testAbi, testContractAddress, "allowance", allowanceArgs, &expected, blockNumber)
|
||||
})
|
||||
|
||||
It("gets the allowance for a spending address and holder address on the dai contract at the given block height", func() {
|
||||
infuraIPC := "https://mainnet.infura.io/J5Vd2fRtGsw0zZ0Ov3BL"
|
||||
rawRpcClient, err := rpc.Dial(infuraIPC)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
rpcClient := client.NewRpcClient(rawRpcClient, infuraIPC)
|
||||
ethClient := ethclient.NewClient(rawRpcClient)
|
||||
blockChainClient := client.NewEthClient(ethClient)
|
||||
node := node.MakeNode(rpcClient)
|
||||
transactionConverter := rpc2.NewRpcTransactionConverter(ethClient)
|
||||
blockChain := geth.NewBlockChain(blockChainClient, node, transactionConverter)
|
||||
realGetter := every_block.NewGetter(blockChain)
|
||||
|
||||
testTokenHolderAddress := common.HexToAddress("0x2cccc4b4708b318a6290511aac75d6c3dbe0cf9f")
|
||||
testTokenSpenderAddress := common.HexToAddress("0x2cccc4b4708b318a6290511aac75d6c3dbe0cf9f")
|
||||
|
||||
hashArgs := []common.Address{testTokenHolderAddress, testTokenSpenderAddress}
|
||||
allowanceArgs := make([]interface{}, len(hashArgs))
|
||||
for i, s := range hashArgs {
|
||||
allowanceArgs[i] = s
|
||||
}
|
||||
|
||||
result, err := realGetter.GetAllowance(constants.DaiAbiString, constants.DaiContractAddress, blockNumber, allowanceArgs)
|
||||
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
expectedResult := big.Int{}
|
||||
expectedResult.SetString("0", 10)
|
||||
Expect(result.String()).To(Equal(expectedResult.String()))
|
||||
})
|
||||
|
||||
It("returns an error if the call to the blockchain fails", func() {
|
||||
blockChain := fakes.NewMockBlockChain()
|
||||
blockChain.SetFetchContractDataErr(fakes.FakeError)
|
||||
errorGetter := every_block.NewGetter(blockChain)
|
||||
result, err := errorGetter.GetAllowance("", "", 0, nil)
|
||||
|
||||
Expect(result.String()).To(Equal("0"))
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("allowance"))
|
||||
Expect(err.Error()).To(ContainSubstring(fakes.FakeError.Error()))
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
@@ -23,7 +23,11 @@ import (
|
||||
// Interface definition for a generic ERC20 token repository
|
||||
type ERC20RepositoryInterface interface {
|
||||
CreateSupply(supply TokenSupply) error
|
||||
CreateBalance(balance TokenBalance) error
|
||||
CreateAllowance(allowance TokenAllowance) error
|
||||
MissingSupplyBlocks(startingBlock, highestBlock int64, tokenAddress string) ([]int64, error)
|
||||
MissingBalanceBlocks(startingBlock, highestBlock int64, tokenAddress, holderAddress string) ([]int64, error)
|
||||
MissingAllowanceBlocks(startingBlock, highestBlock int64, tokenAddress, holderAddress, spenderAddress string) ([]int64, error)
|
||||
}
|
||||
|
||||
// Generic ERC20 token Repo struct
|
||||
@@ -52,9 +56,11 @@ func newRepositoryError(err error, msg string, blockNumber int64) error {
|
||||
|
||||
// Constant error definitions
|
||||
const (
|
||||
GetBlockError = "Error fetching block number %d: %s"
|
||||
InsertTokenSupplyError = "Error inserting token_supply for block number %d: %s"
|
||||
MissingBlockError = "Error finding missing token_supply records starting at block %d: %s"
|
||||
GetBlockError = "Error fetching block number %d: %s"
|
||||
InsertTokenSupplyError = "Error inserting token_supply for block number %d: %s"
|
||||
InsertTokenBalanceError = "Error inserting token_balance for block number %d: %s"
|
||||
InsertTokenAllowanceError = "Error inserting token_allowance for block number %d: %s"
|
||||
MissingBlockError = "Error finding missing token_supply records starting at block %d: %s"
|
||||
)
|
||||
|
||||
// Supply methods
|
||||
@@ -100,3 +106,97 @@ func (tsp *ERC20TokenRepository) MissingSupplyBlocks(startingBlock, highestBlock
|
||||
}
|
||||
return blockNumbers, err
|
||||
}
|
||||
|
||||
// Balance methods
|
||||
// This method inserts the balance for a given token contract address and token owner address at a given block height into the token_balance table
|
||||
func (tsp *ERC20TokenRepository) CreateBalance(balance TokenBalance) error {
|
||||
var blockId int
|
||||
err := tsp.DB.Get(&blockId, `SELECT id FROM blocks WHERE number = $1 AND eth_node_id = $2`, balance.BlockNumber, tsp.NodeID)
|
||||
if err != nil {
|
||||
return newRepositoryError(err, GetBlockError, balance.BlockNumber)
|
||||
}
|
||||
|
||||
_, err = tsp.DB.Exec(
|
||||
`INSERT INTO token_balance (balance, token_address, block_id, token_holder_address)
|
||||
VALUES($1, $2, $3, $4)`,
|
||||
balance.Value, balance.TokenAddress, blockId, balance.TokenHolderAddress)
|
||||
if err != nil {
|
||||
return newRepositoryError(err, InsertTokenBalanceError, balance.BlockNumber)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This method returns an array of blocks that are missing a token_balance entry for a given token contract address and token owner address
|
||||
func (tsp *ERC20TokenRepository) MissingBalanceBlocks(startingBlock, highestBlock int64, tokenAddress, holderAddress string) ([]int64, error) {
|
||||
blockNumbers := make([]int64, 0)
|
||||
|
||||
err := tsp.DB.Select(
|
||||
&blockNumbers,
|
||||
`SELECT number FROM BLOCKS
|
||||
LEFT JOIN token_balance ON blocks.id = block_id
|
||||
AND token_address = $1
|
||||
AND token_holder_address = $2
|
||||
WHERE block_id ISNULL
|
||||
AND eth_node_id = $3
|
||||
AND number >= $4
|
||||
AND number <= $5
|
||||
LIMIT 20`,
|
||||
tokenAddress,
|
||||
holderAddress,
|
||||
tsp.NodeID,
|
||||
startingBlock,
|
||||
highestBlock,
|
||||
)
|
||||
if err != nil {
|
||||
return []int64{}, newRepositoryError(err, MissingBlockError, startingBlock)
|
||||
}
|
||||
return blockNumbers, err
|
||||
}
|
||||
|
||||
// Allowance methods
|
||||
// This method inserts the allowance for a given token contract address, token owner address, and token spender address at a given block height into the
|
||||
func (tsp *ERC20TokenRepository) CreateAllowance(allowance TokenAllowance) error {
|
||||
var blockId int
|
||||
err := tsp.DB.Get(&blockId, `SELECT id FROM blocks WHERE number = $1 AND eth_node_id = $2`, allowance.BlockNumber, tsp.NodeID)
|
||||
if err != nil {
|
||||
return newRepositoryError(err, GetBlockError, allowance.BlockNumber)
|
||||
}
|
||||
|
||||
_, err = tsp.DB.Exec(
|
||||
`INSERT INTO token_allowance (allowance, token_address, block_id, token_holder_address, token_spender_address)
|
||||
VALUES($1, $2, $3, $4, $5)`,
|
||||
allowance.Value, allowance.TokenAddress, blockId, allowance.TokenHolderAddress, allowance.TokenSpenderAddress)
|
||||
if err != nil {
|
||||
return newRepositoryError(err, InsertTokenAllowanceError, allowance.BlockNumber)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// This method returns an array of blocks that are missing a token_allowance entry for a given token contract address, token owner address, and token spender address
|
||||
func (tsp *ERC20TokenRepository) MissingAllowanceBlocks(startingBlock, highestBlock int64, tokenAddress, holderAddress, spenderAddress string) ([]int64, error) {
|
||||
blockNumbers := make([]int64, 0)
|
||||
|
||||
err := tsp.DB.Select(
|
||||
&blockNumbers,
|
||||
`SELECT number FROM BLOCKS
|
||||
LEFT JOIN token_allowance ON blocks.id = block_id
|
||||
AND token_address = $1
|
||||
AND token_holder_address = $2
|
||||
AND token_spender_address = $3
|
||||
WHERE block_id ISNULL
|
||||
AND eth_node_id = $4
|
||||
AND number >= $5
|
||||
AND number <= $6
|
||||
LIMIT 20`,
|
||||
tokenAddress,
|
||||
holderAddress,
|
||||
spenderAddress,
|
||||
tsp.NodeID,
|
||||
startingBlock,
|
||||
highestBlock,
|
||||
)
|
||||
if err != nil {
|
||||
return []int64{}, newRepositoryError(err, MissingBlockError, startingBlock)
|
||||
}
|
||||
return blockNumbers, err
|
||||
}
|
||||
|
||||
@@ -182,6 +182,335 @@ var _ = Describe("ERC20 Token Supply Repository", func() {
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ERC20 Token Balance Repository", func() {
|
||||
var db *postgres.DB
|
||||
var blockId int64
|
||||
var blockNumber int64
|
||||
var repository every_block.ERC20TokenRepository
|
||||
var blockRepository repositories.BlockRepository
|
||||
testTokenAddress := "abc"
|
||||
testHolderAddress := "def"
|
||||
|
||||
BeforeEach(func() {
|
||||
db = test_helpers.CreateNewDatabase()
|
||||
repository = every_block.ERC20TokenRepository{DB: db}
|
||||
_, err := db.Query(`DELETE FROM token_balance`)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
blockRepository = *repositories.NewBlockRepository(db)
|
||||
blockNumber = rand.Int63()
|
||||
blockId = test_helpers.CreateBlock(blockNumber, blockRepository)
|
||||
})
|
||||
|
||||
Describe("Create", func() {
|
||||
It("creates a token balance record", func() {
|
||||
balance := balanceOfModel(blockNumber, testTokenAddress, testHolderAddress, "100")
|
||||
err := repository.CreateBalance(balance)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
dbResult := test_helpers.TokenBalanceDBRow{}
|
||||
expectedTokenBalance := test_helpers.TokenBalanceDBRow{
|
||||
Balance: int64(100),
|
||||
BlockID: blockId,
|
||||
TokenAddress: testTokenAddress,
|
||||
TokenHolderAddress: testHolderAddress,
|
||||
}
|
||||
|
||||
var count int
|
||||
err = repository.DB.QueryRowx(`SELECT count(*) FROM token_balance`).Scan(&count)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(count).To(Equal(1))
|
||||
|
||||
err = repository.DB.QueryRowx(`SELECT * FROM token_balance`).StructScan(&dbResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbResult.Balance).To(Equal(expectedTokenBalance.Balance))
|
||||
Expect(dbResult.BlockID).To(Equal(expectedTokenBalance.BlockID))
|
||||
Expect(dbResult.TokenAddress).To(Equal(expectedTokenBalance.TokenAddress))
|
||||
Expect(dbResult.TokenHolderAddress).To(Equal(expectedTokenBalance.TokenHolderAddress))
|
||||
})
|
||||
|
||||
It("returns an error if fetching the block's id from the database fails", func() {
|
||||
errorBalance := balanceOfModel(-1, "", "", "")
|
||||
err := repository.CreateBalance(errorBalance)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("sql"))
|
||||
Expect(err.Error()).To(ContainSubstring("block number -1"))
|
||||
})
|
||||
|
||||
It("returns an error if inserting the token_balance fails", func() {
|
||||
errorBalance := balanceOfModel(blockNumber, "", "", "")
|
||||
err := repository.CreateBalance(errorBalance)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("pq"))
|
||||
Expect(err.Error()).To(ContainSubstring("token_balance for block number"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("When there are multiple nodes", func() {
|
||||
var node2DB *postgres.DB
|
||||
var node2BlockRepo *repositories.BlockRepository
|
||||
var node2BlockId int64
|
||||
var node2TokenSupplyRepo every_block.ERC20TokenRepository
|
||||
var tokenBalance every_block.TokenBalance
|
||||
|
||||
BeforeEach(func() {
|
||||
node2DB = createDbForAnotherNode()
|
||||
|
||||
//create another block with the same number on node2
|
||||
node2BlockRepo = repositories.NewBlockRepository(node2DB)
|
||||
node2BlockId = test_helpers.CreateBlock(blockNumber, *node2BlockRepo)
|
||||
|
||||
tokenBalance = balanceOfModel(blockNumber, "abc", "def", "100")
|
||||
node2TokenSupplyRepo = every_block.ERC20TokenRepository{DB: node2DB}
|
||||
})
|
||||
|
||||
It("only creates token_balance records for the current node (node2)", func() {
|
||||
err := node2TokenSupplyRepo.CreateBalance(tokenBalance)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var tokenBalances []test_helpers.TokenBalanceDBRow
|
||||
err = node2TokenSupplyRepo.DB.Select(&tokenBalances, `SELECT * FROM token_balance`)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(tokenBalances)).To(Equal(1))
|
||||
Expect(tokenBalances[0].BlockID).To(Equal(node2BlockId))
|
||||
})
|
||||
|
||||
It("only includes missing block numbers for the current node", func() {
|
||||
//create token_balance on original node
|
||||
err := repository.CreateBalance(tokenBalance)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
originalNodeMissingBlocks, err := repository.MissingBalanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(originalNodeMissingBlocks)).To(Equal(0))
|
||||
|
||||
node2MissingBlocks, err := node2TokenSupplyRepo.MissingBalanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(node2MissingBlocks)).To(Equal(1))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("MissingBlocks", func() {
|
||||
It("returns the block numbers for which an associated TokenBalance record hasn't been created", func() {
|
||||
createTokenBalanceFor(repository, blockNumber, testTokenAddress, testHolderAddress)
|
||||
|
||||
newBlockNumber := blockNumber + 1
|
||||
test_helpers.CreateBlock(newBlockNumber, blockRepository)
|
||||
blocks, err := repository.MissingBalanceBlocks(blockNumber, newBlockNumber, testTokenAddress, testHolderAddress)
|
||||
|
||||
Expect(blocks).To(ConsistOf(newBlockNumber))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("only returns blocks within the given range", func() {
|
||||
newBlockNumber := blockNumber + 1
|
||||
test_helpers.CreateBlock(newBlockNumber, blockRepository)
|
||||
blocks, err := repository.MissingBalanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress)
|
||||
|
||||
Expect(blocks).NotTo(ConsistOf(newBlockNumber))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("does not return numbers that already have an associated TokenBalance record", func() {
|
||||
createTokenBalanceFor(repository, blockNumber, testTokenAddress, testHolderAddress)
|
||||
blocks, err := repository.MissingBalanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress)
|
||||
|
||||
Expect(blocks).To(BeEmpty())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
It("deletes the token balance record when the associated block is deleted", func() {
|
||||
err := repository.CreateBalance(every_block.TokenBalance{
|
||||
BlockNumber: blockNumber,
|
||||
TokenAddress: testTokenAddress,
|
||||
TokenHolderAddress: testHolderAddress,
|
||||
Value: "0",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var count int
|
||||
err = repository.DB.QueryRowx(`SELECT count(*) FROM token_balance`).Scan(&count)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(count).To(Equal(1))
|
||||
|
||||
_, err = db.Query(`DELETE FROM blocks`)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = repository.DB.QueryRowx(`SELECT count(*) FROM token_balance`).Scan(&count)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(count).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
var _ = Describe("ERC20 Token Allowance Repository", func() {
|
||||
var db *postgres.DB
|
||||
var blockId int64
|
||||
var blockNumber int64
|
||||
var repository every_block.ERC20TokenRepository
|
||||
var blockRepository repositories.BlockRepository
|
||||
testTokenAddress := "abc"
|
||||
testHolderAddress := "def"
|
||||
testSpenderAddress := "ghi"
|
||||
|
||||
BeforeEach(func() {
|
||||
db = test_helpers.CreateNewDatabase()
|
||||
repository = every_block.ERC20TokenRepository{DB: db}
|
||||
_, err := db.Query(`DELETE FROM token_allowance`)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
blockRepository = *repositories.NewBlockRepository(db)
|
||||
blockNumber = rand.Int63()
|
||||
blockId = test_helpers.CreateBlock(blockNumber, blockRepository)
|
||||
})
|
||||
|
||||
Describe("Create", func() {
|
||||
It("creates a token balance record", func() {
|
||||
allowance := allowanceModel(blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress, "100")
|
||||
err := repository.CreateAllowance(allowance)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
dbResult := test_helpers.TokenAllowanceDBRow{}
|
||||
expectedTokenAllowance := test_helpers.TokenAllowanceDBRow{
|
||||
Allowance: int64(100),
|
||||
BlockID: blockId,
|
||||
TokenAddress: testTokenAddress,
|
||||
TokenHolderAddress: testHolderAddress,
|
||||
TokenSpenderAddress: testSpenderAddress,
|
||||
}
|
||||
|
||||
var count int
|
||||
err = repository.DB.QueryRowx(`SELECT count(*) FROM token_allowance`).Scan(&count)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(count).To(Equal(1))
|
||||
|
||||
err = repository.DB.QueryRowx(`SELECT * FROM token_allowance`).StructScan(&dbResult)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(dbResult.Allowance).To(Equal(expectedTokenAllowance.Allowance))
|
||||
Expect(dbResult.BlockID).To(Equal(expectedTokenAllowance.BlockID))
|
||||
Expect(dbResult.TokenAddress).To(Equal(expectedTokenAllowance.TokenAddress))
|
||||
Expect(dbResult.TokenHolderAddress).To(Equal(expectedTokenAllowance.TokenHolderAddress))
|
||||
})
|
||||
|
||||
It("returns an error if fetching the block's id from the database fails", func() {
|
||||
errorAllowance := allowanceModel(-1, "", "", "", "")
|
||||
err := repository.CreateAllowance(errorAllowance)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("sql"))
|
||||
Expect(err.Error()).To(ContainSubstring("block number -1"))
|
||||
})
|
||||
|
||||
It("returns an error if inserting the token_allowance fails", func() {
|
||||
errorAllowance := allowanceModel(blockNumber, "", "", "", "")
|
||||
err := repository.CreateAllowance(errorAllowance)
|
||||
|
||||
Expect(err).To(HaveOccurred())
|
||||
Expect(err.Error()).To(ContainSubstring("pq"))
|
||||
Expect(err.Error()).To(ContainSubstring("token_allowance for block number"))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("When there are multiple nodes", func() {
|
||||
var node2DB *postgres.DB
|
||||
var node2BlockRepo *repositories.BlockRepository
|
||||
var node2BlockId int64
|
||||
var node2TokenSupplyRepo every_block.ERC20TokenRepository
|
||||
var tokenAllowance every_block.TokenAllowance
|
||||
|
||||
BeforeEach(func() {
|
||||
node2DB = createDbForAnotherNode()
|
||||
|
||||
//create another block with the same number on node2
|
||||
node2BlockRepo = repositories.NewBlockRepository(node2DB)
|
||||
node2BlockId = test_helpers.CreateBlock(blockNumber, *node2BlockRepo)
|
||||
|
||||
tokenAllowance = allowanceModel(blockNumber, "abc", "def", "ghi", "100")
|
||||
node2TokenSupplyRepo = every_block.ERC20TokenRepository{DB: node2DB}
|
||||
})
|
||||
|
||||
It("only creates token_allowance records for the current node (node2)", func() {
|
||||
err := node2TokenSupplyRepo.CreateAllowance(tokenAllowance)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var tokenAllowances []test_helpers.TokenAllowanceDBRow
|
||||
err = node2TokenSupplyRepo.DB.Select(&tokenAllowances, `SELECT * FROM token_allowance`)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(tokenAllowances)).To(Equal(1))
|
||||
Expect(tokenAllowances[0].BlockID).To(Equal(node2BlockId))
|
||||
})
|
||||
|
||||
It("only includes missing block numbers for the current node", func() {
|
||||
//create token_allowance on original node
|
||||
err := repository.CreateAllowance(tokenAllowance)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
originalNodeMissingBlocks, err := repository.MissingAllowanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(originalNodeMissingBlocks)).To(Equal(0))
|
||||
|
||||
node2MissingBlocks, err := node2TokenSupplyRepo.MissingAllowanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(len(node2MissingBlocks)).To(Equal(1))
|
||||
})
|
||||
})
|
||||
|
||||
Describe("MissingBlocks", func() {
|
||||
It("returns the block numbers for which an associated TokenAllowance record hasn't been created", func() {
|
||||
createTokenAllowanceFor(repository, blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
|
||||
newBlockNumber := blockNumber + 1
|
||||
test_helpers.CreateBlock(newBlockNumber, blockRepository)
|
||||
blocks, err := repository.MissingAllowanceBlocks(blockNumber, newBlockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
|
||||
Expect(blocks).To(ConsistOf(newBlockNumber))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("only returns blocks within the given range", func() {
|
||||
newBlockNumber := blockNumber + 1
|
||||
test_helpers.CreateBlock(newBlockNumber, blockRepository)
|
||||
blocks, err := repository.MissingAllowanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
|
||||
Expect(blocks).NotTo(ConsistOf(newBlockNumber))
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
|
||||
It("does not return numbers that already have an associated TokenAllowance record", func() {
|
||||
createTokenAllowanceFor(repository, blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
blocks, err := repository.MissingAllowanceBlocks(blockNumber, blockNumber, testTokenAddress, testHolderAddress, testSpenderAddress)
|
||||
|
||||
Expect(blocks).To(BeEmpty())
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
})
|
||||
})
|
||||
|
||||
It("deletes the token balance record when the associated block is deleted", func() {
|
||||
err := repository.CreateAllowance(every_block.TokenAllowance{
|
||||
BlockNumber: blockNumber,
|
||||
TokenAddress: testTokenAddress,
|
||||
TokenHolderAddress: testHolderAddress,
|
||||
TokenSpenderAddress: testSpenderAddress,
|
||||
Value: "0",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
var count int
|
||||
err = repository.DB.QueryRowx(`SELECT count(*) FROM token_allowance`).Scan(&count)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(count).To(Equal(1))
|
||||
|
||||
_, err = db.Query(`DELETE FROM blocks`)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
|
||||
err = repository.DB.QueryRowx(`SELECT count(*) FROM token_allowance`).Scan(&count)
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
Expect(count).To(Equal(0))
|
||||
})
|
||||
})
|
||||
|
||||
func supplyModel(blockNumber int64, tokenAddress, supplyValue string) every_block.TokenSupply {
|
||||
return every_block.TokenSupply{
|
||||
Value: supplyValue,
|
||||
@@ -190,6 +519,25 @@ func supplyModel(blockNumber int64, tokenAddress, supplyValue string) every_bloc
|
||||
}
|
||||
}
|
||||
|
||||
func balanceOfModel(blockNumber int64, tokenAddress, holderAddress, supplyValue string) every_block.TokenBalance {
|
||||
return every_block.TokenBalance{
|
||||
Value: supplyValue,
|
||||
TokenAddress: tokenAddress,
|
||||
TokenHolderAddress: holderAddress,
|
||||
BlockNumber: blockNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func allowanceModel(blockNumber int64, tokenAddress, holderAddress, spenderAddress, supplyValue string) every_block.TokenAllowance {
|
||||
return every_block.TokenAllowance{
|
||||
Value: supplyValue,
|
||||
TokenAddress: tokenAddress,
|
||||
TokenHolderAddress: holderAddress,
|
||||
TokenSpenderAddress: spenderAddress,
|
||||
BlockNumber: blockNumber,
|
||||
}
|
||||
}
|
||||
|
||||
func createTokenSupplyFor(repository every_block.ERC20TokenRepository, blockNumber int64, tokenAddress string) {
|
||||
err := repository.CreateSupply(every_block.TokenSupply{
|
||||
BlockNumber: blockNumber,
|
||||
@@ -199,6 +547,27 @@ func createTokenSupplyFor(repository every_block.ERC20TokenRepository, blockNumb
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createTokenBalanceFor(repository every_block.ERC20TokenRepository, blockNumber int64, tokenAddress, holderAddress string) {
|
||||
err := repository.CreateBalance(every_block.TokenBalance{
|
||||
BlockNumber: blockNumber,
|
||||
TokenAddress: tokenAddress,
|
||||
TokenHolderAddress: holderAddress,
|
||||
Value: "0",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createTokenAllowanceFor(repository every_block.ERC20TokenRepository, blockNumber int64, tokenAddress, holderAddress, spenderAddress string) {
|
||||
err := repository.CreateAllowance(every_block.TokenAllowance{
|
||||
BlockNumber: blockNumber,
|
||||
TokenAddress: tokenAddress,
|
||||
TokenHolderAddress: holderAddress,
|
||||
TokenSpenderAddress: spenderAddress,
|
||||
Value: "0",
|
||||
})
|
||||
Expect(err).NotTo(HaveOccurred())
|
||||
}
|
||||
|
||||
func createDbForAnotherNode() *postgres.DB {
|
||||
anotherNode := core.Node{
|
||||
GenesisBlock: "GENESIS",
|
||||
|
||||
Reference in New Issue
Block a user