Merge pull request #37 from cosmwasm/contract-msg-send-tokens

Contract msg send tokens
This commit is contained in:
Ethan Frey 2020-01-21 14:17:58 +01:00 committed by GitHub
commit b5f34a732c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 123 additions and 30 deletions

2
go.mod
View File

@ -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

2
go.sum
View File

@ -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=

View File

@ -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)
if stderr != nil {
return bank.MsgSend{}, sdk.ErrInvalidAddress(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
}
toAddr, stderr := sdk.AccAddressFromBech32(msg.ToAddress)
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.ToAddress)
return bank.MsgSend{}, sdk.ErrInvalidAddress(from)
}
toAddr, stderr := sdk.AccAddressFromBech32(to)
if stderr != nil {
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)
@ -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")

View File

@ -6,6 +6,7 @@ import (
"os"
"testing"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
wasmTypes "github.com/confio/go-cosmwasm/types"
@ -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)
@ -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)
@ -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(),
@ -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)
@ -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)
@ -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)
}
}
}