Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Contract msg send tokens #37

Merged
merged 2 commits into from
Jan 21, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ go 1.13

require (
github.com/btcsuite/btcd v0.0.0-20190807005414-4063feeff79a // indirect
github.com/confio/go-cosmwasm v0.6.1
github.com/confio/go-cosmwasm v0.6.2
github.com/cosmos/cosmos-sdk v0.34.4-0.20191114141721-d4c831e63ad3
github.com/cosmos/go-bip39 v0.0.0-20180819234021-555e2067c45d // indirect
github.com/golang/mock v1.3.1 // indirect
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ github.com/confio/go-cosmwasm v0.6.0 h1:6MsfozR4IWb+V9TgVhDoGvcEs0ItBCqHg4pGbMaf
github.com/confio/go-cosmwasm v0.6.0/go.mod h1:pHipRby+f3cv97QPLELkzOAlNs/s87uDyhc+SnMn7L4=
github.com/confio/go-cosmwasm v0.6.1 h1:ifjLWE4T0mKngoq+ubZdtfGJFNWyOeQpfJWNO9y6WKI=
github.com/confio/go-cosmwasm v0.6.1/go.mod h1:pHipRby+f3cv97QPLELkzOAlNs/s87uDyhc+SnMn7L4=
github.com/confio/go-cosmwasm v0.6.2 h1:UMi8mP6VjID1QMPnLMW6xP6zdn7hXmgQTOYSwQgCN0k=
github.com/confio/go-cosmwasm v0.6.2/go.mod h1:pHipRby+f3cv97QPLELkzOAlNs/s87uDyhc+SnMn7L4=
github.com/coreos/bbolt v1.3.2/go.mod h1:iRUV2dpdMOn7Bo10OQBFzIJO9kkE559Wcmn+qkEiiKk=
github.com/coreos/etcd v3.3.10+incompatible/go.mod h1:uF7uidLiAD3TWHmW31ZFd/JWoc32PjwdhPthX9715RE=
github.com/coreos/go-etcd v2.0.0+incompatible/go.mod h1:Jez6KQU2B/sWsbdaef3ED8NzMklzPG4d5KIOhIy30Tk=
Expand Down
48 changes: 28 additions & 20 deletions x/wasm/internal/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -296,46 +296,55 @@ func (k Keeper) dispatchMessages(ctx sdk.Context, contract exported.Account, msg
}

func (k Keeper) dispatchMessage(ctx sdk.Context, contract exported.Account, msg wasmTypes.CosmosMsg) error {
// we check each type (pointers would make it easier to test if set)
// maybe use this instead for the arg?
contractAddr := contract.GetAddress()
if msg.Send != nil {
sendMsg, err := convertCosmosSendMsg(msg.Send)
if err != nil {
return err
}
return k.handleSdkMessage(ctx, contract, sendMsg)
return k.sendTokens(ctx, contractAddr, msg.Send.FromAddress, msg.Send.ToAddress, msg.Send.Amount)
} else if msg.Contract != nil {
targetAddr, stderr := sdk.AccAddressFromBech32(msg.Contract.ContractAddr)
if stderr != nil {
return sdk.ErrInvalidAddress(msg.Contract.ContractAddr)
}
// TODO: use non nil payment once we update go-cosmwasm (ContractMsg contains optional payment)
_, err := k.Execute(ctx, targetAddr, contract.GetAddress(), []byte(msg.Contract.Msg), nil)
err := k.sendTokens(ctx, contractAddr, contractAddr.String(), targetAddr.String(), msg.Contract.Send)
if err != nil {
return err
}
_, err = k.Execute(ctx, targetAddr, contractAddr, []byte(msg.Contract.Msg), nil)
return err // may be nil
} else if msg.Opaque != nil {
msg, err := ParseOpaqueMsg(k.cdc, msg.Opaque)
if err != nil {
return err
}
return k.handleSdkMessage(ctx, contract, msg)
return k.handleSdkMessage(ctx, contractAddr, msg)
}
// what is it?
panic(fmt.Sprintf("Unknown CosmosMsg: %#v", msg))
}

func convertCosmosSendMsg(msg *wasmTypes.SendMsg) (bank.MsgSend, sdk.Error) {
fromAddr, stderr := sdk.AccAddressFromBech32(msg.FromAddress)
func (k Keeper) sendTokens(ctx sdk.Context, signer sdk.AccAddress, origin string, target string, tokens []wasmTypes.Coin) error {
if len(tokens) == 0 {
return nil
}
msg, err := convertCosmosSendMsg(origin, target, tokens)
if err != nil {
return err
}
return k.handleSdkMessage(ctx, signer, msg)
}

func convertCosmosSendMsg(from string, to string, coins []wasmTypes.Coin) (bank.MsgSend, sdk.Error) {
fromAddr, stderr := sdk.AccAddressFromBech32(from)
if stderr != nil {
return bank.MsgSend{}, sdk.ErrInvalidAddress(msg.FromAddress)
return bank.MsgSend{}, sdk.ErrInvalidAddress(from)
}
toAddr, stderr := sdk.AccAddressFromBech32(msg.ToAddress)
toAddr, stderr := sdk.AccAddressFromBech32(to)
if stderr != nil {
return bank.MsgSend{}, sdk.ErrInvalidAddress(msg.ToAddress)
return bank.MsgSend{}, sdk.ErrInvalidAddress(to)
}

var coins sdk.Coins
for _, coin := range msg.Amount {
var toSend sdk.Coins
for _, coin := range coins {
amount, ok := sdk.NewIntFromString(coin.Amount)
if !ok {
return bank.MsgSend{}, sdk.ErrInvalidCoins(coin.Amount + coin.Denom)
Expand All @@ -344,19 +353,18 @@ func convertCosmosSendMsg(msg *wasmTypes.SendMsg) (bank.MsgSend, sdk.Error) {
Denom: coin.Denom,
Amount: amount,
}
coins = append(coins, c)
toSend = append(toSend, c)
}
sendMsg := bank.MsgSend{
FromAddress: fromAddr,
ToAddress: toAddr,
Amount: coins,
Amount: toSend,
}
return sendMsg, nil
}

func (k Keeper) handleSdkMessage(ctx sdk.Context, contract exported.Account, msg sdk.Msg) error {
func (k Keeper) handleSdkMessage(ctx sdk.Context, contractAddr sdk.Address, msg sdk.Msg) error {
// make sure this account can send it
contractAddr := contract.GetAddress()
for _, acct := range msg.GetSigners() {
if !acct.Equals(contractAddr) {
return sdkErrors.Wrap(sdkErrors.ErrUnauthorized, "contract doesn't have permission")
Expand Down
99 changes: 91 additions & 8 deletions x/wasm/internal/keeper/mask_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"

wasmTypes "github.com/confio/go-cosmwasm/types"
Expand All @@ -29,7 +30,7 @@ type reflectPayload struct {
Msg wasmTypes.CosmosMsg `json:"msg"`
}

func TestMaskSend(t *testing.T) {
func TestMaskReflectOpaque(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
Expand Down Expand Up @@ -61,7 +62,6 @@ func TestMaskSend(t *testing.T) {
}
transferBz, err := json.Marshal(transfer)
require.NoError(t, err)
// TODO: switch order of args Instantiate vs Execute (caller/code vs contract/caller), (msg/coins vs coins/msg)
_, err = keeper.Execute(ctx, contractAddr, creator, transferBz, nil)
require.NoError(t, err)

Expand All @@ -71,7 +71,6 @@ func TestMaskSend(t *testing.T) {
checkAccount(t, ctx, accKeeper, fred, nil)

// bob can send contract's tokens to fred (using SendMsg)
// TODO: fix this upstream
msg := wasmTypes.CosmosMsg{
Send: &wasmTypes.SendMsg{
FromAddress: contractAddr.String(),
Expand All @@ -89,7 +88,6 @@ func TestMaskSend(t *testing.T) {
}
reflectSendBz, err := json.Marshal(reflectSend)
require.NoError(t, err)
// TODO: switch order of args Instantiate vs Execute (caller/code vs contract/caller), (msg/coins vs coins/msg)
_, err = keeper.Execute(ctx, contractAddr, bob, reflectSendBz, nil)
require.NoError(t, err)

Expand Down Expand Up @@ -117,7 +115,6 @@ func TestMaskSend(t *testing.T) {
reflectOpaqueBz, err := json.Marshal(reflectOpaque)
require.NoError(t, err)

// TODO: switch order of args Instantiate vs Execute (caller/code vs contract/caller), (msg/coins vs coins/msg)
_, err = keeper.Execute(ctx, contractAddr, bob, reflectOpaqueBz, nil)
require.NoError(t, err)

Expand All @@ -128,12 +125,98 @@ func TestMaskSend(t *testing.T) {
checkAccount(t, ctx, accKeeper, bob, deposit)
}

func TestMaskReflectContractSend(t *testing.T) {
tempDir, err := ioutil.TempDir("", "wasm")
require.NoError(t, err)
defer os.RemoveAll(tempDir)
ctx, accKeeper, keeper := CreateTestInput(t, false, tempDir)

deposit := sdk.NewCoins(sdk.NewInt64Coin("denom", 100000))
creator := createFakeFundedAccount(ctx, accKeeper, deposit)
_, _, bob := keyPubAddr()

// upload mask code
maskCode, err := ioutil.ReadFile("./testdata/mask.wasm")
require.NoError(t, err)
maskID, err := keeper.Create(ctx, creator, maskCode, "", "")
require.NoError(t, err)
require.Equal(t, uint64(1), maskID)

// upload hackatom escrow code
escrowCode, err := ioutil.ReadFile("./testdata/contract.wasm")
require.NoError(t, err)
escrowID, err := keeper.Create(ctx, creator, escrowCode, "", "")
require.NoError(t, err)
require.Equal(t, uint64(2), escrowID)

// creator instantiates a contract and gives it tokens
maskStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 40000))
maskAddr, err := keeper.Instantiate(ctx, maskID, creator, []byte("{}"), maskStart)
require.NoError(t, err)
require.NotEmpty(t, maskAddr)

// now we set contract as verifier of an escrow
initMsg := InitMsg{
Verifier: maskAddr,
Beneficiary: bob,
}
initMsgBz, err := json.Marshal(initMsg)
require.NoError(t, err)
escrowStart := sdk.NewCoins(sdk.NewInt64Coin("denom", 25000))
escrowAddr, err := keeper.Instantiate(ctx, escrowID, creator, initMsgBz, escrowStart)
require.NoError(t, err)
require.NotEmpty(t, escrowAddr)

// let's make sure all balances make sense
checkAccount(t, ctx, accKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // 100k - 40k - 25k
checkAccount(t, ctx, accKeeper, maskAddr, maskStart)
checkAccount(t, ctx, accKeeper, escrowAddr, escrowStart)
checkAccount(t, ctx, accKeeper, bob, nil)

// now for the trick.... we reflect a message through the mask to call the escrow
// we also send an additional 14k tokens there.
// this should reduce the mask balance by 14k (to 26k)
// this 14k is added to the escrow, then the entire balance is sent to bob (total: 39k)
approveMsg := "{}"
msg := wasmTypes.CosmosMsg{
Contract: &wasmTypes.ContractMsg{
ContractAddr: escrowAddr.String(),
Msg: approveMsg,
Send: []wasmTypes.Coin{{
Denom: "denom",
Amount: "14000",
}},
},
}
reflectSend := MaskHandleMsg{
Reflect: &reflectPayload{
Msg: msg,
},
}
reflectSendBz, err := json.Marshal(reflectSend)
require.NoError(t, err)
_, err = keeper.Execute(ctx, maskAddr, creator, reflectSendBz, nil)
require.NoError(t, err)

// did this work???
checkAccount(t, ctx, accKeeper, creator, sdk.NewCoins(sdk.NewInt64Coin("denom", 35000))) // same as before
checkAccount(t, ctx, accKeeper, maskAddr, sdk.NewCoins(sdk.NewInt64Coin("denom", 26000))) // 40k - 14k (from send)
checkAccount(t, ctx, accKeeper, escrowAddr, sdk.Coins{}) // emptied reserved
checkAccount(t, ctx, accKeeper, bob, sdk.NewCoins(sdk.NewInt64Coin("denom", 39000))) // all escrow of 25k + 14k

}

func checkAccount(t *testing.T, ctx sdk.Context, accKeeper auth.AccountKeeper, addr sdk.AccAddress, expected sdk.Coins) {
acct := accKeeper.GetAccount(ctx, addr)
if expected == nil {
require.Nil(t, acct)
assert.Nil(t, acct)
} else {
require.NotNil(t, acct)
require.Equal(t, acct.GetCoins(), expected)
assert.NotNil(t, acct)
if expected.Empty() {
// there is confusion between nil and empty slice... let's just treat them the same
assert.True(t, acct.GetCoins().Empty())
} else {
assert.Equal(t, acct.GetCoins(), expected)
}
}
}