Skip to content

Commit

Permalink
feature: Smart Contract Tax Whitelist (#301)
Browse files Browse the repository at this point in the history
Co-authored-by: alchemist-ti <[email protected]>
Co-authored-by: nghuyenthevinh2000 <[email protected]>
Co-authored-by: Inon Man <[email protected]>
  • Loading branch information
4 people authored Aug 8, 2023
1 parent 63acef9 commit b0bc33f
Show file tree
Hide file tree
Showing 10 changed files with 277 additions and 14 deletions.
3 changes: 2 additions & 1 deletion app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,8 @@ func NewAppKeepers(
appKeepers.AccountKeeper, appKeepers.BankKeeper,
appKeepers.MarketKeeper, appKeepers.OracleKeeper,
appKeepers.StakingKeeper, appKeepers.DistrKeeper,
distrtypes.ModuleName)
&appKeepers.WasmKeeper, distrtypes.ModuleName,
)

wasmConfig, err := wasm.ReadWasmConfig(appOpts)
if err != nil {
Expand Down
1 change: 1 addition & 0 deletions custom/auth/ante/expected_keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ type TreasuryKeeper interface {
GetTaxCap(ctx sdk.Context, denom string) (taxCap sdk.Int)
GetBurnSplitRate(ctx sdk.Context) sdk.Dec
HasBurnTaxExemptionAddress(ctx sdk.Context, addresses ...string) bool
HasBurnTaxExemptionContract(ctx sdk.Context, address string) bool
GetMinInitialDepositRatio(ctx sdk.Context) sdk.Dec
}

Expand Down
4 changes: 3 additions & 1 deletion custom/auth/ante/tax.go
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,9 @@ func FilterMsgAndComputeTax(ctx sdk.Context, tk TreasuryKeeper, msgs ...sdk.Msg)
taxes = taxes.Add(computeTax(ctx, tk, msg.Funds)...)

case *wasm.MsgExecuteContract:
taxes = taxes.Add(computeTax(ctx, tk, msg.Funds)...)
if !tk.HasBurnTaxExemptionContract(ctx, msg.Contract) {
taxes = taxes.Add(computeTax(ctx, tk, msg.Funds)...)
}

case *authz.MsgExec:
messages, err := msg.GetMessages()
Expand Down
82 changes: 72 additions & 10 deletions custom/auth/ante/tax_test.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package ante_test

import (
"encoding/json"
"fmt"
"os"
"time"

cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types"
"github.com/cosmos/cosmos-sdk/testutil/testdata"
Expand All @@ -12,6 +15,7 @@ import (
banktypes "github.com/cosmos/cosmos-sdk/x/bank/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/classic-terra/core/v2/custom/auth/ante"
core "github.com/classic-terra/core/v2/types"
Expand Down Expand Up @@ -813,10 +817,11 @@ func (suite *AnteTestSuite) TestTaxExemption() {
feeAmt := int64(1000)

cases := []struct {
name string
msgSigner cryptotypes.PrivKey
msgCreator func() []sdk.Msg
expectedFeeAmount int64
name string
msgSigner cryptotypes.PrivKey
msgCreator func() []sdk.Msg
minFeeAmount int64
expectProceeds int64
}{
{
name: "MsgSend(exemption -> exemption)",
Expand All @@ -829,7 +834,8 @@ func (suite *AnteTestSuite) TestTaxExemption() {

return msgs
},
expectedFeeAmount: 0,
minFeeAmount: 0,
expectProceeds: 0,
}, {
name: "MsgSend(normal -> normal)",
msgSigner: privs[2],
Expand All @@ -842,7 +848,8 @@ func (suite *AnteTestSuite) TestTaxExemption() {
return msgs
},
// tax this one hence burn amount is fee amount
expectedFeeAmount: feeAmt,
minFeeAmount: feeAmt,
expectProceeds: feeAmt,
}, {
name: "MsgSend(exemption -> normal), MsgSend(exemption -> exemption)",
msgSigner: privs[0],
Expand All @@ -857,7 +864,8 @@ func (suite *AnteTestSuite) TestTaxExemption() {
return msgs
},
// tax this one hence burn amount is fee amount
expectedFeeAmount: feeAmt,
minFeeAmount: feeAmt,
expectProceeds: feeAmt,
}, {
name: "MsgSend(exemption -> exemption), MsgMultiSend(exemption -> normal, exemption -> exemption)",
msgSigner: privs[0],
Expand Down Expand Up @@ -892,7 +900,58 @@ func (suite *AnteTestSuite) TestTaxExemption() {

return msgs
},
expectedFeeAmount: feeAmt * 2,
minFeeAmount: feeAmt * 2,
expectProceeds: feeAmt * 2,
}, {
name: "MsgExecuteContract(exemption), MsgExecuteContract(normal)",
msgSigner: privs[3],
msgCreator: func() []sdk.Msg {
sendAmount := int64(1000000)
sendCoins := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, sendAmount))
// get wasm code for wasm contract create and instantiate
wasmCode, err := os.ReadFile("./testdata/hackatom.wasm")
suite.Require().NoError(err)
per := wasmkeeper.NewDefaultPermissionKeeper(suite.app.WasmKeeper)
// set wasm default params
suite.app.WasmKeeper.SetParams(suite.ctx, wasmtypes.DefaultParams())
// wasm create
CodeID, _, err := per.Create(suite.ctx, addrs[0], wasmCode, nil)
suite.Require().NoError(err)
// params for contract init
r := wasmkeeper.HackatomExampleInitMsg{Verifier: addrs[0], Beneficiary: addrs[0]}
bz, err := json.Marshal(r)
suite.Require().NoError(err)
// change block time for contract instantiate
suite.ctx = suite.ctx.WithBlockTime(time.Date(2020, time.April, 22, 12, 0, 0, 0, time.UTC))
// instantiate contract then set the contract address to tax exemption
addr, _, err := per.Instantiate(suite.ctx, CodeID, addrs[0], nil, bz, "my label", nil)
suite.Require().NoError(err)
suite.app.TreasuryKeeper.AddBurnTaxExemptionAddress(suite.ctx, addr.String())
// instantiate contract then not set to tax exemption
addr1, _, err := per.Instantiate(suite.ctx, CodeID, addrs[0], nil, bz, "my label", nil)
suite.Require().NoError(err)

var msgs []sdk.Msg
// msg and signatures
msg1 := &wasmtypes.MsgExecuteContract{
Sender: addrs[0].String(),
Contract: addr.String(),
Msg: []byte{},
Funds: sendCoins,
}
msgs = append(msgs, msg1)

msg2 := &wasmtypes.MsgExecuteContract{
Sender: addrs[3].String(),
Contract: addr1.String(),
Msg: []byte{},
Funds: sendCoins,
}
msgs = append(msgs, msg2)
return msgs
},
minFeeAmount: feeAmt,
expectProceeds: feeAmt,
},
}

Expand Down Expand Up @@ -929,7 +988,7 @@ func (suite *AnteTestSuite) TestTaxExemption() {
}

// msg and signatures
feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, c.expectedFeeAmount))
feeAmount := sdk.NewCoins(sdk.NewInt64Coin(core.MicroSDRDenom, c.minFeeAmount))
gasLimit := testdata.NewTestGasLimit()
require.NoError(suite.txBuilder.SetMsgs(c.msgCreator()...))
suite.txBuilder.SetFeeAmount(feeAmount)
Expand All @@ -945,7 +1004,10 @@ func (suite *AnteTestSuite) TestTaxExemption() {
// check fee collector
feeCollector := ak.GetModuleAccount(suite.ctx, types.FeeCollectorName)
amountFee := bk.GetBalance(suite.ctx, feeCollector.GetAddress(), core.MicroSDRDenom)
require.Equal(amountFee, sdk.NewCoin("usdr", sdk.NewInt(c.minFeeAmount)))

require.Equal(amountFee, sdk.NewCoin("usdr", sdk.NewInt(c.expectedFeeAmount)))
// check tax proceeds
taxProceeds := suite.app.TreasuryKeeper.PeekEpochTaxProceeds(suite.ctx)
require.Equal(taxProceeds, sdk.NewCoins(sdk.NewCoin("usdr", sdk.NewInt(c.expectProceeds))))
}
}
Binary file added custom/auth/ante/testdata/hackatom.wasm
Binary file not shown.
134 changes: 134 additions & 0 deletions scripts/contract-tax-exemption-after-upgrade.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
#!/bin/sh

set +e

BINARY=_build/new/terrad
HACK_ATOM="./custom/auth/ante/testdata/hackatom.wasm"
KEYRING_BACKEND="test"
HOME=mytestnet
CHAIN_ID=localterra
KEY="test0"
KEY1="test1"
KEY1="test2"
DENOM=uluna

# store stargate-tester
echo "... stores a wasm"
addr=$($BINARY keys show $KEY -a --home $HOME --keyring-backend $KEYRING_BACKEND)
addr1=$($BINARY keys show $KEY1 -a --home $HOME --keyring-backend $KEYRING_BACKEND)
addr2=$($BINARY keys show $KEY1 -a --home $HOME --keyring-backend $KEYRING_BACKEND)

out=$($BINARY tx wasm store ${HACK_ATOM} --from $KEY --output json --gas auto --gas-adjustment 2.3 --fees 100000000uluna --chain-id $CHAIN_ID --home $HOME --keyring-backend $KEYRING_BACKEND -y)
code=$(echo $out | jq -r '.code')
if [ "$code" != "0" ]; then
echo "... Could not store contract" >&2
echo $out >&2
exit $code
fi
sleep 10
txhash=$(echo $out | jq -r '.txhash')
echo "$txhash"
code_id=$($BINARY q tx $txhash -o json | jq -r '.raw_log' | jq -r '.[0].events[1].attributes[1].value')
echo "code_id $code_id"

# instantiate stargate-tester
echo "... instantiates contract"
msg=$(jq -n '
{
"verifier":"'$addr'",
"beneficiary":"'$addr'"
}')
echo $msg
out=$($BINARY tx wasm instantiate $code_id "$msg" --from $KEY --output json --gas auto --gas-adjustment 2.3 --label "hackatom" --fees 20000000uluna --no-admin --chain-id $CHAIN_ID --home $HOME --keyring-backend $KEYRING_BACKEND -y)
code=$(echo $out | jq -r '.code')
if [ "$code" != "0" ]; then
echo "... Could not instantiate contract" >&2
echo $out >&2
exit $code
fi
sleep 10

txhash=$(echo $out | jq -r '.txhash')
echo "$txhash"
contract_addr=$($BINARY q tx $txhash -o json | jq -r '.raw_log' | jq -r '.[0].events[0].attributes[0].value')
echo "contract_addr $contract_addr"

# query verifier
echo "... query verifier address"
msg='{"verifier":{}}'
out=$($BINARY query wasm contract-state smart $contract_addr $msg --output json)
echo $out

# execute release
echo "... execute release"
msg=$(jq -n '
{
"nothing":{}
}')
echo $msg
out=$($BINARY tx wasm execute $contract_addr "$msg" --from $KEY --amount 1000000uluna --output json --gas auto --fees 400000000uluna --gas-adjustment 2.3 --chain-id $CHAIN_ID --home $HOME --keyring-backend $KEYRING_BACKEND -y -o json)
echo $out
txhash=$(echo $out | jq -r '.txhash')
echo $txhash
sleep 5
gasUsed=$($BINARY q tx --type=hash $txhash -o json | jq -r '.gas_used')
echo gas used $gasUsed before adding to tax exemption list

sleep 5
echo "... query tax-proceeds before adding to tax exemption list"
tax_proceeds_0=$($BINARY q treasury tax-proceeds)

# add contract_addr to burn tax exemption list
echo "add contract $contract_addr to burn tax exemption list"
out=$($BINARY tx gov submit-proposal add-burn-tax-exemption-address "$contract_addr" --title "burn tax exemption address" --description "burn tax exemption address" --from $KEY --keyring-backend $KEYRING_BACKEND --chain-id $CHAIN_ID --home $HOME -y)
echo $out

sleep 5
out=$($BINARY tx gov deposit 1 "20000000${DENOM}" --from $KEY --keyring-backend $KEYRING_BACKEND --chain-id $CHAIN_ID --home $HOME -y)
echo $out

sleep 5
out=$($BINARY tx gov vote 1 yes --from $KEY --keyring-backend $KEYRING_BACKEND --chain-id $CHAIN_ID --home $HOME -y)
echo $out

sleep 5
out=$($BINARY tx gov vote 1 yes --from $KEY1 --keyring-backend $KEYRING_BACKEND --chain-id $CHAIN_ID --home $HOME -y)
echo $out

sleep 5

while true; do
PROPOSAL_STATUS=$(./_build/new/terrad q gov proposal 1 --output=json | jq ".status" -r)
echo $PROPOSAL_STATUS
if [ $PROPOSAL_STATUS = "PROPOSAL_STATUS_PASSED" ]; then
break
else
sleep 10
fi
done

echo ""
echo "CHECK ADDRESS AFTER ADDING BURN TAX EXEMPTION LIST"
echo ""

# check burn tax exemption address
./_build/new/terrad q treasury burn-tax-exemption-list -o json | jq ".addresses"

# execute release
msg=$(jq -n '
{
"nothing":{}
}')
echo $msg
out=$($BINARY tx wasm execute $contract_addr "$msg" --from $KEY --output json --gas auto --gas-adjustment 2.3 --chain-id $CHAIN_ID --home $HOME --keyring-backend $KEYRING_BACKEND -y -o json)
echo $out
txhash=$(echo $out | jq -r '.txhash')
echo $txhash
sleep 5
gasUsed=$($BINARY q tx --type=hash $txhash -o json | jq -r '.gas_used')
echo gas used $gasUsed after adding to tax exemption list

echo "... print tax-proceeds before adding to tax exemption list"
echo $tax_proceeds_0
echo "... query tax-proceeds after adding to tax exemption list, should be the same as before"
$BINARY q treasury tax-proceeds
2 changes: 1 addition & 1 deletion scripts/run-node.sh
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ CONTINUE=${CONTINUE:-"false"}
HOME_DIR=mytestnet

if [ "$CONTINUE" == "true" ]; then
$BINARY start --home $HOME
$BINARY start --home $HOME_DIR
exit 0
fi

Expand Down
3 changes: 2 additions & 1 deletion scripts/wasm_command.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ ADDITIONAL_PRE_SCRIPTS=scripts/wasm/token-migration.sh ADDITIONAL_AFTER_SCRIPTS=
ADDITIONAL_PRE_SCRIPTS=scripts/wasm/wasm-deploy.sh ADDITIONAL_AFTER_SCRIPTS=scripts/wasm/wasm-tx-check.sh bash scripts/upgrade-test.sh
FORK=true ADDITIONAL_AFTER_SCRIPTS=scripts/wasm/stargate-after-upgrade.sh bash scripts/upgrade-test.sh
./_build/new/terrad tx wasm execute terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 '{"mint":{"token_id":"'4'","owner":"'terra1p20jxrllewr5meecvhtmpddexr0kkz3tdename'"}}' --from test0 --output json --gas auto --gas-adjustment 2.3 --fees 20000000uluna --chain-id test --home mytestnet --keyring-backend test -y
./_build/new/terrad q wasm contract-state smart terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 '{"all_tokens":{}}' --chain-id test --home mytestnet
./_build/new/terrad q wasm contract-state smart terra18vd8fpwxzck93qlwghaj6arh4p7c5n896xzem5 '{"all_tokens":{}}' --chain-id test --home mytestnet
FORK=true ADDITIONAL_AFTER_SCRIPTS=scripts/contract-tax-exemption-after-upgrade.sh bash scripts/upgrade-test.sh
23 changes: 23 additions & 0 deletions x/treasury/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import (
"github.com/tendermint/tendermint/libs/log"

"github.com/classic-terra/core/v2/x/treasury/types"

wasmkeeper "github.com/CosmWasm/wasmd/x/wasm/keeper"
)

// Keeper of the treasury store
Expand All @@ -28,6 +30,7 @@ type Keeper struct {
stakingKeeper types.StakingKeeper
distrKeeper types.DistributionKeeper
oracleKeeper types.OracleKeeper
wasmKeeper *wasmkeeper.Keeper

distributionModuleName string
}
Expand All @@ -41,6 +44,7 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey,
oracleKeeper types.OracleKeeper,
stakingKeeper types.StakingKeeper,
distrKeeper types.DistributionKeeper,
wasmKeeper *wasmkeeper.Keeper,
distributionModuleName string,
) Keeper {
// ensure treasury module account is set
Expand Down Expand Up @@ -68,6 +72,7 @@ func NewKeeper(cdc codec.BinaryCodec, storeKey sdk.StoreKey,
oracleKeeper: oracleKeeper,
stakingKeeper: stakingKeeper,
distrKeeper: distrKeeper,
wasmKeeper: wasmKeeper,
distributionModuleName: distributionModuleName,
}
}
Expand Down Expand Up @@ -371,6 +376,8 @@ func (k Keeper) RemoveBurnTaxExemptionAddress(ctx sdk.Context, address string) e
return nil
}

// HasBurnTaxExemptionAddress returns true if all provided addresses are in the
// tax exemption whitelist
func (k Keeper) HasBurnTaxExemptionAddress(ctx sdk.Context, addresses ...string) bool {
sub := prefix.NewStore(ctx.KVStore(k.storeKey), types.BurnTaxExemptionListPrefix)

Expand All @@ -382,3 +389,19 @@ func (k Keeper) HasBurnTaxExemptionAddress(ctx sdk.Context, addresses ...string)

return true
}

// HasBurnTaxExemptionContract returns true if a provided address is a
// smart contract AND is in the tax exemption list
func (k Keeper) HasBurnTaxExemptionContract(ctx sdk.Context, address string) bool {
contractAddr, err := sdk.AccAddressFromBech32(address)
if err != nil {
return false
}

info := k.wasmKeeper.GetContractInfo(ctx, contractAddr)
if info == nil {
return false
}

return k.HasBurnTaxExemptionAddress(ctx, address)
}
Loading

0 comments on commit b0bc33f

Please sign in to comment.