Skip to content

Commit

Permalink
feat(wasm): authz handler for signless contract execution
Browse files Browse the repository at this point in the history
  • Loading branch information
keruch committed Dec 6, 2024
1 parent 7bf7515 commit 99e5050
Show file tree
Hide file tree
Showing 7 changed files with 600 additions and 16 deletions.
2 changes: 0 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -573,8 +573,6 @@ github.com/dymensionxyz/cosmos-sdk v0.46.17-0.20241128210616-e9dfe47b8c73 h1:A0O
github.com/dymensionxyz/cosmos-sdk v0.46.17-0.20241128210616-e9dfe47b8c73/go.mod h1:VPUuzF+l+ekSGPV7VVB8m0OMQfwp3QdKWNZjvkU3A1U=
github.com/dymensionxyz/cosmosclient v0.4.2-beta.0.20241121093220-e0d7ad456fbd h1:V89QyOFM84o9w0iFdctMU6So8SS/Xt32JWAXGqJduT0=
github.com/dymensionxyz/cosmosclient v0.4.2-beta.0.20241121093220-e0d7ad456fbd/go.mod h1:3weqpVj/TqTFpC0LjEB3H+HZSpm7BrQ1QkEg1Ahy6KY=
github.com/dymensionxyz/dymension-rdk v1.6.1-0.20241204193918-4fb6a8d95889 h1:AZ1cskz6smqxkUkCbbbOWfuhMqE0Y1AwbgKSs3zIj+A=
github.com/dymensionxyz/dymension-rdk v1.6.1-0.20241204193918-4fb6a8d95889/go.mod h1:AA0rq+4H+yn/Agnx5L8ermhhny3FYgCv6UW9DzK5vss=
github.com/dymensionxyz/dymension-rdk v1.6.1-0.20241206101450-08627417ea7d h1:WtRE6IcI+N8QlseRt8OfUWJLL22l5CEDVfJWoytHK9A=
github.com/dymensionxyz/dymension-rdk v1.6.1-0.20241206101450-08627417ea7d/go.mod h1:AA0rq+4H+yn/Agnx5L8ermhhny3FYgCv6UW9DzK5vss=
github.com/dymensionxyz/dymint v1.2.0-rc01.0.20241203150638-c0e39f93d729 h1:2HtdhYN0DSqBQTcSeK9+QXBJz8CF7u1YrLu8odkCFSY=
Expand Down
22 changes: 22 additions & 0 deletions proto/rollapp/wasm/authz.proto
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
syntax = "proto3";
package rollapp.wasm;

import "gogoproto/gogo.proto";
import "cosmos_proto/cosmos.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/dymensionxyz/rollapp-wasm/x/wasm";

// ContractExecutionAuthorization defines authorization for wasm execute.
message ContractExecutionAuthorization {
option (cosmos_proto.implements_interface) = "cosmos.authz.v1beta1.Authorization";

// Contracts is a list of allowed contracts. Optional.
repeated string contracts = 1;

// SpendLimits defines spending limits for contracts interactions.
repeated cosmos.base.v1beta1.Coin spend_limit = 2 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
];
}
2 changes: 1 addition & 1 deletion scripts/settlement/register_sequencer_to_hub.sh
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ fi
#Register Sequencer
# DESCRIPTION="{\"Moniker\":\"${ROLLAPP_CHAIN_ID}-sequencer\",\"Identity\":\"\",\"Website\":\"\",\"SecurityContact\":\"\",\"Details\":\"\"}"
SEQ_PUB_KEY="$("$ROLLAPP_EXECUTABLE" dymint show-sequencer)"
BOND_AMOUNT="$("$SETTLEMENT_EXECUTABLE" q sequencer params -o json --node "$HUB_RPC_URL" | jq -r '.params.min_bond.amount')$("$SETTLEMENT_EXECUTABLE" q sequencer params -o json --node "$HUB_RPC_URL" | jq -r '.params.min_bond.denom')"
BOND_AMOUNT="$("$SETTLEMENT_EXECUTABLE" q sequencer params -o json --node "$HUB_RPC_URL" | jq -r '.params.kick_threshold.amount')$("$SETTLEMENT_EXECUTABLE" q sequencer params -o json --node "$HUB_RPC_URL" | jq -r '.params.kick_threshold.denom')"

echo "$BOND_AMOUNT"

Expand Down
34 changes: 21 additions & 13 deletions scripts/wasm/deploy_contract.sh
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,8 @@ ROLLAPP_KEY_NAME_GENESIS="rol-user"
ROLLAPP_GENESIS_ADDR="$($EXECUTABLE keys show $ROLLAPP_KEY_NAME_GENESIS | grep "address:" | cut -d' ' -f3)"

# Store code for cw20_base contract
$EXECUTABLE tx wasm store ./scripts/bytecode/cw20_base.wasm --from rol-user --gas 5000000 --yes

sleep 5
$EXECUTABLE tx wasm store ./scripts/bytecode/cw20_base.wasm --from rol-user --gas auto --gas-adjustment=1.2 --gas-prices "1000000000$BASE_DENOM" --yes
sleep 7

CW20_CODE_ID="$($EXECUTABLE q wasm list-code | grep "code_id:" | tail -n 1 | cut -d' ' -f3 | tr -d '"')"

Expand All @@ -19,27 +18,34 @@ INIT_CW20=$(cat <<EOF
"initial_balances": [{
"address": "$ROLLAPP_GENESIS_ADDR",
"amount": "100000000000"
}]
}],
"mint": {
"minter": "$ROLLAPP_GENESIS_ADDR",
"cap": "10000000000000"
}
}
EOF
)
$EXECUTABLE tx wasm instantiate $CW20_CODE_ID "$INIT_CW20" --label test --no-admin --from $ROLLAPP_KEY_NAME_GENESIS --yes
sleep 2
CW20_ADDR=$($EXECUTABLE q wasm list-contract-by-code $CW20_CODE_ID --output json | jq -r '.contracts[0]' )

$EXECUTABLE tx wasm instantiate "$CW20_CODE_ID" "$INIT_CW20" --label test --no-admin --from $ROLLAPP_KEY_NAME_GENESIS --gas auto --gas-adjustment=1.2 --gas-prices "1000000000$BASE_DENOM" --yes
sleep 7

CW20_ADDR=$($EXECUTABLE q wasm list-contract-by-code "$CW20_CODE_ID" --output json | jq -r '.contracts[0]')
echo "Token contract deployed at: $CW20_ADDR"

# Query rol-user balances
QUERY_MSG=$(cat <<EOF
{"balance":{"address":"$ROLLAPP_GENESIS_ADDR"}}
EOF
)

balance=$($EXECUTABLE q wasm contract-state smart $CW20_ADDR "$QUERY_MSG" | grep "balance:" | cut -d' ' -f4 | tr -d '"')
echo "User $ROLLAPP_GENESIS_ADDR has balance $balance for contract $CW20_ADDR"


# Store code for ics20 contract
$EXECUTABLE tx wasm store ./scripts/bytecode/cw20_ics20.wasm --from $ROLLAPP_KEY_NAME_GENESIS --gas 5000000 --yes
sleep 5
$EXECUTABLE tx wasm store ./scripts/bytecode/cw20_ics20.wasm --from $ROLLAPP_KEY_NAME_GENESIS --gas auto --gas-adjustment=1.2 --gas-prices "1000000000$BASE_DENOM" --yes
sleep 7

ICS20_CODE_ID="$($EXECUTABLE q wasm list-code | grep "code_id:" | tail -n 1 | cut -d' ' -f3 | tr -d '"')"

INIT_ICS20=$(cat <<EOF
Expand All @@ -52,9 +58,11 @@ INIT_ICS20=$(cat <<EOF
}
EOF
)
$EXECUTABLE tx wasm instantiate $ICS20_CODE_ID "$INIT_ICS20" --label ics20 --no-admin --from rol-user --gas 50000000 --yes
sleep 2
ICS20_ADDR=$($EXECUTABLE q wasm list-contract-by-code $ICS20_CODE_ID --output json | jq -r '.contracts[0]' )

$EXECUTABLE tx wasm instantiate "$ICS20_CODE_ID" "$INIT_ICS20" --label ics20 --no-admin --from rol-user --gas auto --gas-adjustment=1.2 --gas-prices "1000000000$BASE_DENOM" --yes
sleep 7

ICS20_ADDR=$($EXECUTABLE q wasm list-contract-by-code "$ICS20_CODE_ID" --output json | jq -r '.contracts[0]' )

echo "ICS20 contract deployed at: $ICS20_ADDR"

Expand Down
75 changes: 75 additions & 0 deletions x/wasm/authz.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package wasm

import (
errors "errors"
"fmt"
"slices"

wasmtypes "github.com/CosmWasm/wasmd/x/wasm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
authztypes "github.com/cosmos/cosmos-sdk/x/authz"
)

var _ authztypes.Authorization = &ContractExecutionAuthorization{}

func NewContractExecutionAuthorization(contracts []string, spendLimit sdk.Coins) *ContractExecutionAuthorization {
return &ContractExecutionAuthorization{
Contracts: contracts,
SpendLimit: spendLimit,
}
}

// MsgTypeURL implements Authorization.MsgTypeURL.
func (a *ContractExecutionAuthorization) MsgTypeURL() string {
return sdk.MsgTypeURL(&wasmtypes.MsgExecuteContract{})
}

func (a *ContractExecutionAuthorization) Accept(_ sdk.Context, msg sdk.Msg) (authztypes.AcceptResponse, error) {
m, ok := msg.(*wasmtypes.MsgExecuteContract)
if !ok {
return authztypes.AcceptResponse{}, errors.New("invalid message type")
}

// Check whitelisted contracts if specified
if len(a.Contracts) > 0 && !slices.Contains(a.Contracts, m.Contract) {
return authztypes.AcceptResponse{}, errors.New("contract not authorized")
}

// Check spend limits if specified
if !a.SpendLimit.Empty() {
if m.Funds.IsAnyGT(a.SpendLimit) {
return authztypes.AcceptResponse{}, errors.New("exceeds spend limit")
}

// Update spend limits
a.SpendLimit = a.SpendLimit.Sub(m.Funds...)
if a.SpendLimit.Empty() {
return authztypes.AcceptResponse{Accept: true, Delete: true}, nil
}
}

return authztypes.AcceptResponse{Accept: true, Updated: a}, nil
}

func (a *ContractExecutionAuthorization) ValidateBasic() error {
// Check for duplicate contracts
contractSet := make(map[string]struct{})
for _, contract := range a.Contracts {
if _, err := sdk.AccAddressFromBech32(contract); err != nil {
return fmt.Errorf("invalid contract address: %s: %w", contract, err)
}
if _, exists := contractSet[contract]; exists {
return fmt.Errorf("duplicate contract address: %s", contract)
}
contractSet[contract] = struct{}{}
}

if !a.SpendLimit.IsZero() {
err := a.SpendLimit.Validate()
if err != nil {
return fmt.Errorf("validate spend limit: %s", err)
}
}

return nil
}
Loading

0 comments on commit 99e5050

Please sign in to comment.