Skip to content

Commit

Permalink
feat: build transaction twice, make estimate gas fee.
Browse files Browse the repository at this point in the history
  • Loading branch information
Zhangguiguang committed Apr 25, 2023
1 parent 6741e58 commit eccb587
Show file tree
Hide file tree
Showing 12 changed files with 208 additions and 208 deletions.
63 changes: 44 additions & 19 deletions core/sui/chain.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import (

const (
MaxGasBudget = 90000000
MinGasBudget = 1100000
// MaxGasForMerge = 10000000

MaxGasForPay = 10000000
Expand Down Expand Up @@ -186,8 +187,7 @@ func (c *Chain) BatchFetchTransactionStatus(hashListString string) string {
return strings.Join(statuses, ",")
}

// @param gasId gas object to be used in this transaction, the gateway will pick one from the signer's possession if not provided
func (c *Chain) TransferObject(sender, receiver, objectId, gasId string, gasBudget int64) (txn *Transaction, err error) {
func (c *Chain) TransferObject(sender, receiver, objectId string, gasBudget int64) (txn *Transaction, err error) {
defer base.CatchPanicAndMapToBasicError(&err)

senderAddress, err := types.NewAddressFromHex(sender)
Expand All @@ -202,26 +202,18 @@ func (c *Chain) TransferObject(sender, receiver, objectId, gasId string, gasBudg
if err != nil {
return nil, err
}
var gas *types.ObjectId = nil
if gasId != "" {
gas, err = types.NewHexData(gasId)
if err != nil {
return nil, errors.New("Invalid gas object id")
}
}
client, err := c.Client()
if err != nil {
return
}
gasInt := types.NewSafeSuiBigInt(uint64(gasBudget))
tx, err := client.TransferObject(context.Background(), *senderAddress, *receiverAddress, *nftObject, gas, gasInt)
if err != nil {
return
}
return &Transaction{
Txn: *tx,
MaxGasBudget: gasBudget,
}, nil
return c.EstimateTransactionFeeAndRebuildTransaction(uint64(gasBudget), func(maxGas uint64) (*Transaction, error) {
gasInt := types.NewSafeSuiBigInt(maxGas)
txBytes, err := client.TransferObject(context.Background(), *senderAddress, *receiverAddress, *nftObject, nil, gasInt)
if err != nil {
return nil, err
}
return &Transaction{Txn: *txBytes}, nil
})
}

func (c *Chain) GasPrice() (gasprice *base.OptionalString, err error) {
Expand Down Expand Up @@ -251,12 +243,15 @@ func (c *Chain) EstimateGasFee(transaction *Transaction) (fee *base.OptionalStri
if err != nil {
return
}
if !effects.Effects.Data.IsSuccess() {
return nil, errors.New(effects.Effects.Data.V1.Status.Error)
}

gasFee := effects.Effects.Data.GasFee()
if gasFee == 0 {
gasFee = MaxGasBudget
} else {
gasFee = gasFee/10*15 + 14 // >= ceil(fee * 1.5)
gasFee = gasFee/10*11 + 10 // >= ceil(fee * 1.1)
}
transaction.EstimateGasFee = gasFee
gasString := strconv.FormatInt(gasFee, 10)
Expand All @@ -280,3 +275,33 @@ func FaucetFundAccount(address string, faucetUrl string) (h *base.OptionalString
}
return &base.OptionalString{Value: hash}, nil
}

// @param maxGasBudget: the firstly build required gas
// @param builer: the builder should build a transaction, it maybe will invoking twice, the firstly build gas pass the maxGasBudget, the second build will pass the estimate gas.
func (c *Chain) EstimateTransactionFeeAndRebuildTransaction(maxGasBudget uint64, buildTransaction func(gasBudget uint64) (*Transaction, error)) (*Transaction, error) {
txn, err := buildTransaction(maxGasBudget)
if err != nil {
return nil, err
}
_, err = c.EstimateGasFee(txn)
if err != nil {
return nil, err
}
estimateFeeUint := uint64(txn.EstimateGasFee)
if txn.EstimateGasFee < MinGasBudget {
estimateFeeUint = MinGasBudget
}
if estimateFeeUint/5*6 > maxGasBudget && maxGasBudget-estimateFeeUint < 1000000 {
// estimate*1.2 > max && max-estimate < 0.001SUI
// The estimated transaction fee is not much different from the build transaction.
return txn, nil
}

// second call the builder
newTxn, err := buildTransaction(estimateFeeUint)
if err != nil {
return nil, err
}
newTxn.EstimateGasFee = txn.EstimateGasFee
return newTxn, nil
}
31 changes: 12 additions & 19 deletions core/sui/chain_nft.go
Original file line number Diff line number Diff line change
Expand Up @@ -97,37 +97,30 @@ func transformNFT(nft *types.SuiObjectResponse) *base.NFT {
}
}

// @param gasId gas object to be used in this transaction, the gateway will pick one from the signer's possession if not provided
func (c *Chain) MintNFT(creator, name, description, uri, gasId string, gasBudget int64) (txn *Transaction, err error) {
func (c *Chain) MintNFT(creator, name, description, uri string) (txn *Transaction, err error) {
defer base.CatchPanicAndMapToBasicError(&err)

signer, err := types.NewAddressFromHex(creator)
if err != nil {
return nil, errors.New("Invalid creator address")
}
var gas *types.ObjectId = nil
if gasId != "" {
gas, err = types.NewHexData(gasId)
if err != nil {
return nil, errors.New("Invalid gas object id")
}
}
client, err := c.Client()
if err != nil {
return
}
tx, err := client.MintNFT(context.Background(), *signer, name, description, uri, gas, uint64(gasBudget))
if err != nil {
return
}
return &Transaction{
Txn: *tx,
MaxGasBudget: gasBudget,
}, nil
return c.EstimateTransactionFeeAndRebuildTransaction(MaxGasBudget, func(gasBudget uint64) (*Transaction, error) {
txBytes, err := client.MintNFT(context.Background(), *signer, name, description, uri, nil, gasBudget)
if err != nil {
return nil, err
}
return &Transaction{
Txn: *txBytes,
}, nil
})
}

// Just encapsulation and callbacks to method `TransferObject`.
// @param gasId gas object to be used in this transaction, the gateway will pick one from the signer's possession if not provided
func (c *Chain) TransferNFT(sender, receiver, nftId, gasId string, gasBudget int64) (txn *Transaction, err error) {
return c.TransferObject(sender, receiver, nftId, gasId, gasBudget)
func (c *Chain) TransferNFT(sender, receiver, nftId string) (txn *Transaction, err error) {
return c.TransferObject(sender, receiver, nftId, MaxGasBudget)
}
4 changes: 2 additions & 2 deletions core/sui/chain_nft_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ func TestMintNFT(t *testing.T) {
nftDesc = "This is a NFT created by ComingChat"
nftUrl = "https://coming.chat/favicon.ico"
)
txn, err := chain.MintNFT(account.Address(), nftName, nftDesc, nftUrl, "", MaxGasBudget)
txn, err := chain.MintNFT(account.Address(), nftName, nftDesc, nftUrl)
require.Nil(t, err)
signedTxn, err := txn.SignWithAccount(account)
require.Nil(t, err)
Expand All @@ -61,7 +61,7 @@ out:
}
require.NotNil(t, nft)

txn, err := chain.TransferNFT(account.Address(), receiver, nft.Id, "", MaxGasBudget)
txn, err := chain.TransferNFT(account.Address(), receiver, nft.Id)
require.Nil(t, err)
signedTxn, err := txn.SignWithAccount(account)
require.Nil(t, err)
Expand Down
50 changes: 20 additions & 30 deletions core/sui/chain_stake.go
Original file line number Diff line number Diff line change
Expand Up @@ -381,18 +381,18 @@ func (c *Chain) AddDelegation(owner, amount string, validatorAddress string) (tx
if err != nil {
return
}
txBytes, err := cli.RequestAddStake(context.Background(), *signer,
pickedCoins.CoinIds(),
decimal.NewFromBigInt(amountInt, 0),
*validator,
nil, decimal.NewFromInt(maxGasBudgetForStake))
if err != nil {
return
}
return &Transaction{
Txn: *txBytes,
MaxGasBudget: maxGasBudgetForStake,
}, nil
return c.EstimateTransactionFeeAndRebuildTransaction(maxGasBudgetForStake, func(gasBudget uint64) (*Transaction, error) {
gasInt := big.NewInt(0).SetUint64(gasBudget)
txBytes, err := cli.RequestAddStake(context.Background(), *signer,
pickedCoins.CoinIds(),
decimal.NewFromBigInt(amountInt, 0),
*validator,
nil, decimal.NewFromBigInt(gasInt, 0))
if err != nil {
return nil, err
}
return &Transaction{Txn: *txBytes}, nil
})
}

func (c *Chain) WithdrawDelegation(owner, stakeId string) (txn *Transaction, err error) {
Expand All @@ -410,24 +410,14 @@ func (c *Chain) WithdrawDelegation(owner, stakeId string) (txn *Transaction, err
if err != nil {
return
}
allCoins, err := cli.GetSuiCoinsOwnedByAddress(context.Background(), *signer)
if err != nil {
return
}
gasCoin, err := allCoins.PickCoinNoLess(maxGasBudgetForStake)
if err != nil {
return
}
gasId := gasCoin.CoinObjectId
txnBytes, err := cli.RequestWithdrawStake(context.Background(), *signer, *stakeSui, &gasId, decimal.NewFromInt(maxGasBudgetForStake))
if err != nil {
return
}

return &Transaction{
Txn: *txnBytes,
MaxGasBudget: maxGasBudgetForStake,
}, nil
return c.EstimateTransactionFeeAndRebuildTransaction(maxGasBudgetForStake, func(gasBudget uint64) (*Transaction, error) {
gasInt := big.NewInt(0).SetUint64(gasBudget)
txnBytes, err := cli.RequestWithdrawStake(context.Background(), *signer, *stakeSui, nil, decimal.NewFromBigInt(gasInt, 0))
if err != nil {
return nil, err
}
return &Transaction{Txn: *txnBytes}, nil
})
}

func mapRawValidator(v *types.SuiValidatorSummary, apys map[string]float64) *Validator {
Expand Down
15 changes: 9 additions & 6 deletions core/sui/chain_stake_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,11 +68,14 @@ func TestWithdrawDelegation(t *testing.T) {
chain := TestnetChain()
acc := M1Account(t)

if false {
stakeId := "0x5cdb23dacf54329660467b900a2598bb796353fa"
txn, err := chain.WithdrawDelegation(acc.Address(), stakeId)
require.Nil(t, err)
stakedArray, err := chain.GetDelegatedStakes(acc.Address())
require.Nil(t, err)
require.Greater(t, stakedArray.Count(), 0)

simulateCheck(t, chain, &txn.Txn, false)
}
stake := stakedArray.Values[0].(*DelegatedStake)
stakeId := stake.StakeId
txn, err := chain.WithdrawDelegation(acc.Address(), stakeId)
require.Nil(t, err)

simulateCheck(t, chain, &txn.Txn, false)
}
13 changes: 13 additions & 0 deletions core/sui/chain_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,19 @@ func TestFaucet(t *testing.T) {
}
}

func Test_EstimateFee_And_RebuildTxn(t *testing.T) {
account := M1Account(t)
chain := TestnetChain()
token := NewTokenMain(chain)

txn, err := chain.EstimateTransactionFeeAndRebuildTransaction(MaxGasForTransfer, func(gasBudget uint64) (*Transaction, error) {
return token.BuildTransferTransaction(account, account.Address(), SUI(1).String())
})
require.Nil(t, err)

t.Log(txn)
}

func simulateCheck(t *testing.T, chain *Chain, txn *types.TransactionBytes, showJson bool) *types.DryRunTransactionBlockResponse {
cli, err := chain.Client()
require.Nil(t, err)
Expand Down
Loading

0 comments on commit eccb587

Please sign in to comment.