Skip to content

Commit

Permalink
feat(x/feeshare): Allow registering factory contracts (#566)
Browse files Browse the repository at this point in the history
* Allow factory contracts to self register without bindings

* cleanup factory logic

* improve readability of isFactoryContract

* Add register & update docs
  • Loading branch information
Reecepbcups committed Feb 21, 2023
1 parent 70b3033 commit 8d11c10
Show file tree
Hide file tree
Showing 8 changed files with 122 additions and 7 deletions.
4 changes: 4 additions & 0 deletions app/keepers/keepers.go
Original file line number Diff line number Diff line change
Expand Up @@ -403,10 +403,14 @@ func NewAppKeepers(

// Stargate Queries
accepted := wasmkeeper.AcceptedStargateQueries{
// ibc
"/ibc.core.client.v1.Query/ClientState": &ibcclienttypes.QueryClientStateResponse{},
"/ibc.core.client.v1.Query/ConsensusState": &ibcclienttypes.QueryConsensusStateResponse{},
"/ibc.core.connection.v1.Query/Connection": &ibcconnectiontypes.QueryConnectionResponse{},

// governance
"/cosmos.gov.v1beta1.Query/Vote": &govtypes.QueryVoteResponse{},

// token factory
"/osmosis.tokenfactory.v1beta1.Query/Params": &tokenfactorytypes.QueryParamsResponse{},
"/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata": &tokenfactorytypes.QueryDenomAuthorityMetadataResponse{},
Expand Down
6 changes: 6 additions & 0 deletions x/feeshare/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,9 @@ This module is a heavily modified fork of [evmos/x/revenue](https://github.com/e
A big thanks go to the original authors.

[FeeShare Spec](spec/README.md)

---

> [Register a Contract](spec/00_register.md)
> [Update Conrtact Withdraw Address](spec/00_update.md)
2 changes: 1 addition & 1 deletion x/feeshare/client/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ func NewCancelFeeShare() *cobra.Command {
// address of a contract for fee distribution
func NewUpdateFeeShare() *cobra.Command {
cmd := &cobra.Command{
Use: "update [contract_bech32] [",
Use: "update [contract_bech32] [new_withdraw_bech32]",
Short: "Update withdrawer address for a contract registered for feeshare distribution.",
Long: "Update withdrawer address for a contract registered for feeshare distribution. \nOnly the contract admin can update the withdrawer address.",
Args: cobra.ExactArgs(2),
Expand Down
53 changes: 47 additions & 6 deletions x/feeshare/keeper/msg_server.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,36 @@ import (
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"

wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types"
"github.com/CosmosContracts/juno/v13/x/feeshare/types"
)

var _ types.MsgServer = &Keeper{}

func (k Keeper) GetIfContractWasCreatedFromFactory(ctx sdk.Context, contract sdk.AccAddress, info *wasmTypes.ContractInfo) bool {
// This will allow ANYONE to register FeeShare funds to its own contract if it was created from a factory contract
// Note: if there is no admin but a creator made it, then the creator can register it how they wish

creator, err := sdk.AccAddressFromBech32(info.Creator)
if err != nil {
return false
}

isFactoryContract := false

if len(info.Admin) == 0 {
isFactoryContract = k.wasmKeeper.HasContractInfo(ctx, creator)
} else {
admin, err := sdk.AccAddressFromBech32(info.Admin)
if err != nil {
return false
}
isFactoryContract = k.wasmKeeper.HasContractInfo(ctx, admin)
}

return isFactoryContract
}

// GetContractAdminOrCreatorAddress ensures the deployer is the contract's admin OR creator if no admin is set for all msg_server feeshare functions.
func (k Keeper) GetContractAdminOrCreatorAddress(ctx sdk.Context, contract sdk.AccAddress, deployer string) (sdk.AccAddress, error) {
var controllingAccount sdk.AccAddress
Expand Down Expand Up @@ -73,18 +98,34 @@ func (k Keeper) RegisterFeeShare(
return nil, sdkerrors.Wrapf(types.ErrFeeShareAlreadyRegistered, "contract is already registered %s", contract)
}

// Check that the person who signed the message is the wasm contract admin, if so return the deployer address
deployer, err := k.GetContractAdminOrCreatorAddress(ctx, contract, msg.DeployerAddress)
if err != nil {
return nil, err
}

// Get the withdraw address of the contract
withdrawer, err := sdk.AccAddressFromBech32(msg.WithdrawerAddress)
if err != nil {
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid withdrawer address %s", msg.WithdrawerAddress)
}

var deployer sdk.AccAddress

if k.GetIfContractWasCreatedFromFactory(ctx, contract, k.wasmKeeper.GetContractInfo(ctx, contract)) {
// Anyone is allowed to register a contract to itself if it was created from a factory contract
if msg.WithdrawerAddress != msg.ContractAddress {
return nil, sdkerrors.Wrapf(types.ErrFeeShareInvalidWithdrawer, "withdrawer address must be the same as the contract address if it is from a factory contract withdraw:%s contract:%s", msg.WithdrawerAddress, msg.ContractAddress)
}

// set the deployer address to the contract address so it can self register
msg.DeployerAddress = msg.ContractAddress
deployer, err = sdk.AccAddressFromBech32(msg.DeployerAddress)
if err != nil {
return nil, err
}
} else {
// Check that the person who signed the message is the wasm contract admin or creator (if no admin)
deployer, err = k.GetContractAdminOrCreatorAddress(ctx, contract, msg.DeployerAddress)
if err != nil {
return nil, err
}
}

// prevent storing the same address for deployer and withdrawer
feeshare := types.NewFeeShare(contract, deployer, withdrawer)
k.SetFeeShare(ctx, feeshare)
Expand Down
21 changes: 21 additions & 0 deletions x/feeshare/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,7 @@ func (s *IntegrationTestSuite) TestRegisterFeeShare() {
_ = s.FundAccount(s.ctx, sender, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1_000_000))))

contractAddress := s.InstantiateContract(sender.String(), "")
contractAddress2 := s.InstantiateContract(contractAddress, contractAddress)

_, _, withdrawer := testdata.KeyTestPubAddr()

Expand Down Expand Up @@ -160,6 +161,26 @@ func (s *IntegrationTestSuite) TestRegisterFeeShare() {
resp: &types.MsgRegisterFeeShareResponse{},
shouldErr: false,
},
{
desc: "Invalid withdraw address for factory contract",
msg: &types.MsgRegisterFeeShare{
ContractAddress: contractAddress2,
DeployerAddress: sender.String(),
WithdrawerAddress: sender.String(),
},
resp: &types.MsgRegisterFeeShareResponse{},
shouldErr: true,
},
{
desc: "Success register factory contract to itself",
msg: &types.MsgRegisterFeeShare{
ContractAddress: contractAddress2,
DeployerAddress: sender.String(),
WithdrawerAddress: contractAddress2,
},
resp: &types.MsgRegisterFeeShareResponse{},
shouldErr: false,
},
} {
tc := tc
s.Run(tc.desc, func() {
Expand Down
31 changes: 31 additions & 0 deletions x/feeshare/spec/00_register.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
# Register a contract

`junod tx feeshare register [contract_bech32] [withdraw_bech32] --from [key]`

Registers the withdrawal address for the given contract.

## Parameters

`contract_bech32 (string, required)`: The bech32 address of the contract whose interaction fees will be shared.

`withdraw_bech32 (string, required)`: The bech32 address where the interaction fees will be sent every block.

## Description

This command registers the withdrawal address for the given contract. Any time a user interacts with your contract, the funds will be sent to the withdrawal address. It can be any valid address, such as a DAO, normal account, another contract, or a multi-sig.

## Permissions

This command can only be run by the admin of the contract. If there is no admin, then it can only be run by the contract creator.

## Exceptions

```text
withdraw_bech32 can not be the community pool (distribution) address. This is a limitation of the way the SDK handles this module account
```

```text
For contracts created or administered by a contract factory, the withdrawal address can only be the same as the contract address. This can be registered by anyone, but it's unchangeable. This is helpful for SubDAOs or public goods to save fees in the treasury.
If you create a contract like this, it's best to create an execution method for withdrawing fees to an account. To do this, you'll need to save the withdrawal address in the contract's state before uploading a non-migratable contract.
```
11 changes: 11 additions & 0 deletions x/feeshare/spec/00_update.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Update a Contract's Withdrawal Address

This can be changed at any time so long as you are still the admin or creator of a contract with the command:

`junod tx feeshare update [contract] [new_withdraw_address]`

## Update Exception

```text
This can not be done if the contract was created from or is administered by another contract (a contract factory). There is not currently a way for a contract to change its own withdrawal address directly.
```
1 change: 1 addition & 0 deletions x/feeshare/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,4 +11,5 @@ var (
ErrFeeShareNoContractDeployed = sdkerrrors.Register(ModuleName, 3, "no contract deployed")
ErrFeeShareContractNotRegistered = sdkerrrors.Register(ModuleName, 4, "no feeshare registered for contract")
ErrFeeSharePayment = sdkerrrors.Register(ModuleName, 5, "feeshare payment error")
ErrFeeShareInvalidWithdrawer = sdkerrrors.Register(ModuleName, 6, "invalid withdrawer address")
)

0 comments on commit 8d11c10

Please sign in to comment.