Skip to content

Commit 9e306e2

Browse files
authored
feat(x/feeshare): Allow registering factory contracts (CosmosContracts#566)
* Allow factory contracts to self register without bindings * cleanup factory logic * improve readability of isFactoryContract * Add register & update docs
1 parent 2f69983 commit 9e306e2

File tree

8 files changed

+122
-7
lines changed

8 files changed

+122
-7
lines changed

app/keepers/keepers.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,10 +399,14 @@ func NewAppKeepers(
399399

400400
// Stargate Queries
401401
accepted := wasmkeeper.AcceptedStargateQueries{
402+
// ibc
402403
"/ibc.core.client.v1.Query/ClientState": &ibcclienttypes.QueryClientStateResponse{},
403404
"/ibc.core.client.v1.Query/ConsensusState": &ibcclienttypes.QueryConsensusStateResponse{},
404405
"/ibc.core.connection.v1.Query/Connection": &ibcconnectiontypes.QueryConnectionResponse{},
405406

407+
// governance
408+
"/cosmos.gov.v1beta1.Query/Vote": &govtypes.QueryVoteResponse{},
409+
406410
// token factory
407411
"/osmosis.tokenfactory.v1beta1.Query/Params": &tokenfactorytypes.QueryParamsResponse{},
408412
"/osmosis.tokenfactory.v1beta1.Query/DenomAuthorityMetadata": &tokenfactorytypes.QueryDenomAuthorityMetadataResponse{},

x/feeshare/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,9 @@ This module is a heavily modified fork of [evmos/x/revenue](https://github.com/e
66
A big thanks go to the original authors.
77

88
[FeeShare Spec](spec/README.md)
9+
10+
---
11+
12+
> [Register a Contract](spec/00_register.md)
13+
14+
> [Update Conrtact Withdraw Address](spec/00_update.md)

x/feeshare/client/cli/tx.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ func NewCancelFeeShare() *cobra.Command {
108108
// address of a contract for fee distribution
109109
func NewUpdateFeeShare() *cobra.Command {
110110
cmd := &cobra.Command{
111-
Use: "update [contract_bech32] [",
111+
Use: "update [contract_bech32] [new_withdraw_bech32]",
112112
Short: "Update withdrawer address for a contract registered for feeshare distribution.",
113113
Long: "Update withdrawer address for a contract registered for feeshare distribution. \nOnly the contract admin can update the withdrawer address.",
114114
Args: cobra.ExactArgs(2),

x/feeshare/keeper/msg_server.go

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,36 @@ import (
66
sdk "github.com/cosmos/cosmos-sdk/types"
77
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
88

9+
wasmTypes "github.com/CosmWasm/wasmd/x/wasm/types"
910
"github.com/CosmosContracts/juno/v13/x/feeshare/types"
1011
)
1112

1213
var _ types.MsgServer = &Keeper{}
1314

15+
func (k Keeper) GetIfContractWasCreatedFromFactory(ctx sdk.Context, contract sdk.AccAddress, info *wasmTypes.ContractInfo) bool {
16+
// This will allow ANYONE to register FeeShare funds to its own contract if it was created from a factory contract
17+
// Note: if there is no admin but a creator made it, then the creator can register it how they wish
18+
19+
creator, err := sdk.AccAddressFromBech32(info.Creator)
20+
if err != nil {
21+
return false
22+
}
23+
24+
isFactoryContract := false
25+
26+
if len(info.Admin) == 0 {
27+
isFactoryContract = k.wasmKeeper.HasContractInfo(ctx, creator)
28+
} else {
29+
admin, err := sdk.AccAddressFromBech32(info.Admin)
30+
if err != nil {
31+
return false
32+
}
33+
isFactoryContract = k.wasmKeeper.HasContractInfo(ctx, admin)
34+
}
35+
36+
return isFactoryContract
37+
}
38+
1439
// GetContractAdminOrCreatorAddress ensures the deployer is the contract's admin OR creator if no admin is set for all msg_server feeshare functions.
1540
func (k Keeper) GetContractAdminOrCreatorAddress(ctx sdk.Context, contract sdk.AccAddress, deployer string) (sdk.AccAddress, error) {
1641
var controllingAccount sdk.AccAddress
@@ -73,18 +98,34 @@ func (k Keeper) RegisterFeeShare(
7398
return nil, sdkerrors.Wrapf(types.ErrFeeShareAlreadyRegistered, "contract is already registered %s", contract)
7499
}
75100

76-
// Check that the person who signed the message is the wasm contract admin, if so return the deployer address
77-
deployer, err := k.GetContractAdminOrCreatorAddress(ctx, contract, msg.DeployerAddress)
78-
if err != nil {
79-
return nil, err
80-
}
81-
82101
// Get the withdraw address of the contract
83102
withdrawer, err := sdk.AccAddressFromBech32(msg.WithdrawerAddress)
84103
if err != nil {
85104
return nil, sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "invalid withdrawer address %s", msg.WithdrawerAddress)
86105
}
87106

107+
var deployer sdk.AccAddress
108+
109+
if k.GetIfContractWasCreatedFromFactory(ctx, contract, k.wasmKeeper.GetContractInfo(ctx, contract)) {
110+
// Anyone is allowed to register a contract to itself if it was created from a factory contract
111+
if msg.WithdrawerAddress != msg.ContractAddress {
112+
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)
113+
}
114+
115+
// set the deployer address to the contract address so it can self register
116+
msg.DeployerAddress = msg.ContractAddress
117+
deployer, err = sdk.AccAddressFromBech32(msg.DeployerAddress)
118+
if err != nil {
119+
return nil, err
120+
}
121+
} else {
122+
// Check that the person who signed the message is the wasm contract admin or creator (if no admin)
123+
deployer, err = k.GetContractAdminOrCreatorAddress(ctx, contract, msg.DeployerAddress)
124+
if err != nil {
125+
return nil, err
126+
}
127+
}
128+
88129
// prevent storing the same address for deployer and withdrawer
89130
feeshare := types.NewFeeShare(contract, deployer, withdrawer)
90131
k.SetFeeShare(ctx, feeshare)

x/feeshare/keeper/msg_server_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@ func (s *IntegrationTestSuite) TestRegisterFeeShare() {
111111
_ = s.FundAccount(s.ctx, sender, sdk.NewCoins(sdk.NewCoin("stake", sdk.NewInt(1_000_000))))
112112

113113
contractAddress := s.InstantiateContract(sender.String(), "")
114+
contractAddress2 := s.InstantiateContract(contractAddress, contractAddress)
114115

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

@@ -160,6 +161,26 @@ func (s *IntegrationTestSuite) TestRegisterFeeShare() {
160161
resp: &types.MsgRegisterFeeShareResponse{},
161162
shouldErr: false,
162163
},
164+
{
165+
desc: "Invalid withdraw address for factory contract",
166+
msg: &types.MsgRegisterFeeShare{
167+
ContractAddress: contractAddress2,
168+
DeployerAddress: sender.String(),
169+
WithdrawerAddress: sender.String(),
170+
},
171+
resp: &types.MsgRegisterFeeShareResponse{},
172+
shouldErr: true,
173+
},
174+
{
175+
desc: "Success register factory contract to itself",
176+
msg: &types.MsgRegisterFeeShare{
177+
ContractAddress: contractAddress2,
178+
DeployerAddress: sender.String(),
179+
WithdrawerAddress: contractAddress2,
180+
},
181+
resp: &types.MsgRegisterFeeShareResponse{},
182+
shouldErr: false,
183+
},
163184
} {
164185
tc := tc
165186
s.Run(tc.desc, func() {

x/feeshare/spec/00_register.md

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
# Register a contract
2+
3+
`junod tx feeshare register [contract_bech32] [withdraw_bech32] --from [key]`
4+
5+
Registers the withdrawal address for the given contract.
6+
7+
## Parameters
8+
9+
`contract_bech32 (string, required)`: The bech32 address of the contract whose interaction fees will be shared.
10+
11+
`withdraw_bech32 (string, required)`: The bech32 address where the interaction fees will be sent every block.
12+
13+
## Description
14+
15+
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.
16+
17+
## Permissions
18+
19+
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.
20+
21+
## Exceptions
22+
23+
```text
24+
withdraw_bech32 can not be the community pool (distribution) address. This is a limitation of the way the SDK handles this module account
25+
```
26+
27+
```text
28+
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.
29+
30+
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.
31+
```

x/feeshare/spec/00_update.md

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Update a Contract's Withdrawal Address
2+
3+
This can be changed at any time so long as you are still the admin or creator of a contract with the command:
4+
5+
`junod tx feeshare update [contract] [new_withdraw_address]`
6+
7+
## Update Exception
8+
9+
```text
10+
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.
11+
```

x/feeshare/types/errors.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,5 @@ var (
1111
ErrFeeShareNoContractDeployed = sdkerrrors.Register(ModuleName, 3, "no contract deployed")
1212
ErrFeeShareContractNotRegistered = sdkerrrors.Register(ModuleName, 4, "no feeshare registered for contract")
1313
ErrFeeSharePayment = sdkerrrors.Register(ModuleName, 5, "feeshare payment error")
14+
ErrFeeShareInvalidWithdrawer = sdkerrrors.Register(ModuleName, 6, "invalid withdrawer address")
1415
)

0 commit comments

Comments
 (0)