Skip to content

Commit

Permalink
Merge pull request #281 from terra-money/feat/smartaccounts/posttx
Browse files Browse the repository at this point in the history
Feat/smartaccounts/posttx
  • Loading branch information
emidev98 authored Mar 19, 2024
2 parents 48a1b2f + 2871b26 commit f764d50
Show file tree
Hide file tree
Showing 9 changed files with 243 additions and 34 deletions.
7 changes: 4 additions & 3 deletions app/app.go
Original file line number Diff line number Diff line change
Expand Up @@ -272,9 +272,10 @@ func NewTerraApp(
}
postHandler := post.NewPostHandler(
post.HandlerOptions{
FeeShareKeeper: app.Keepers.FeeShareKeeper,
BankKeeper: app.Keepers.BankKeeper,
WasmKeeper: app.Keepers.WasmKeeper,
FeeShareKeeper: app.Keepers.FeeShareKeeper,
BankKeeper: app.Keepers.BankKeeper,
WasmKeeper: app.Keepers.WasmKeeper,
SmartAccountKeeper: &app.Keepers.SmartAccountKeeper,
},
)

Expand Down
5 changes: 5 additions & 0 deletions app/post/post.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package post

import (
feesharepost "github.com/terra-money/core/v2/x/feeshare/post"
smartaccountkeeper "github.com/terra-money/core/v2/x/smartaccount/keeper"
smartaccountpost "github.com/terra-money/core/v2/x/smartaccount/post"
customwasmkeeper "github.com/terra-money/core/v2/x/wasm/keeper"
wasmpost "github.com/terra-money/core/v2/x/wasm/post"

Expand All @@ -12,13 +14,16 @@ type HandlerOptions struct {
FeeShareKeeper feesharepost.FeeShareKeeper
BankKeeper feesharepost.BankKeeper
WasmKeeper customwasmkeeper.Keeper

SmartAccountKeeper *smartaccountkeeper.Keeper
}

func NewPostHandler(options HandlerOptions) sdk.PostHandler {

postDecorators := []sdk.PostDecorator{
feesharepost.NewFeeSharePayoutDecorator(options.FeeShareKeeper, options.BankKeeper, options.WasmKeeper),
wasmpost.NewWasmdDecorator(options.WasmKeeper),
smartaccountpost.NewPostTransactionHookDecorator(options.SmartAccountKeeper, options.WasmKeeper),
}

return sdk.ChainPostDecorators(postDecorators...)
Expand Down
24 changes: 12 additions & 12 deletions integration-tests/src/helpers/mnemonics.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,21 +48,21 @@ export function getMnemonics() {
let wasmContracts = new MnemonicKey({
mnemonic: "degree under tray object thought mercy mushroom captain bus work faint basic twice cube noble man ripple close flush bunker dish spare hungry arm"
})
let mnemonic2 = new MnemonicKey({
let smartaccControllerMnemonic = new MnemonicKey({
mnemonic: "range struggle season mesh antenna delay sell light yard path risk curve brain nut cabin injury dilemma fun comfort crumble today transfer bring draft"
})
let mnemonic3 = new MnemonicKey({
let smartaccPreTxMnemonic = new MnemonicKey({
mnemonic: "giraffe trim element wheel cannon nothing enrich shiver upon output iron recall already fix appear produce fix behind scissors artefact excite tennis into side"
})
let mnemonic4 = new MnemonicKey({
let smartaccPostTxMnemonic = new MnemonicKey({
mnemonic: "run turn cup combine sad toast roof already melt chimney arctic save avocado theory bracket cherry cotton fee once favorite swarm ignore dream element"
})
let mnemonic5 = new MnemonicKey({
mnemonic: "script key fold coyote cage squirrel prevent pole auction slide vintage shoot mirror erosion equip goose capable critic test space sketch monkey eight candy"
})
let smartaccountMnemonic = new MnemonicKey({
let smartaccAuthMnemonic = new MnemonicKey({
mnemonic: "work clap clarify edit explain exact depth ramp law hard feel beauty stumble occur prevent crush distance purpose scrap current describe skirt panther skirt"
})
let mnemonic1 = new MnemonicKey({
mnemonic: "script key fold coyote cage squirrel prevent pole auction slide vintage shoot mirror erosion equip goose capable critic test space sketch monkey eight candy"
})

return {
val1,
Expand All @@ -79,10 +79,10 @@ export function getMnemonics() {
tokenFactoryMnemonic,
ibcHooksMnemonic,
wasmContracts,
mnemonic2,
mnemonic3,
mnemonic4,
mnemonic5,
smartaccountMnemonic,
mnemonic1,
smartaccAuthMnemonic,
smartaccControllerMnemonic,
smartaccPreTxMnemonic,
smartaccPostTxMnemonic,
}
}
8 changes: 4 additions & 4 deletions integration-tests/src/modules/smartaccount/auth.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ describe("Smartaccount Module (https://github.com/terra-money/core/tree/release/
// Prepare environment clients, accounts and wallets
const LCD = getLCDClient();
const accounts = getMnemonics();
const wallet = LCD.chain1.wallet(accounts.mnemonic5);
const controlledAccountAddress = accounts.mnemonic5.accAddress("terra");
const wallet = LCD.chain1.wallet(accounts.smartaccAuthMnemonic);
const controlledAccountAddress = accounts.smartaccAuthMnemonic.accAddress("terra");

const controller = LCD.chain1.wallet(accounts.mnemonic4);
const pubkey = accounts.mnemonic4.publicKey;
const controller = LCD.chain1.wallet(accounts.smartaccControllerMnemonic);
const pubkey = accounts.smartaccControllerMnemonic.publicKey;
expect(pubkey).toBeDefined();

const pubkeybb = pubkey as SimplePublicKey;
Expand Down
195 changes: 195 additions & 0 deletions integration-tests/src/modules/smartaccount/posttx.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,195 @@
import { Coin, Coins, MsgInstantiateContract, MsgSend, MsgStoreCode } from "@terra-money/feather.js";
import { MsgCreateSmartAccount, MsgUpdateTransactionHooks } from "@terra-money/feather.js/dist/core/smartaccount";
import fs from "fs";
import path from 'path';
import { blockInclusion, getLCDClient, getMnemonics } from "../../helpers";

describe("Smartaccount Module (https://github.com/terra-money/core/tree/release/v2.6/x/smartaccount) ", () => {
// Prepare environment clients, accounts and wallets
const LCD = getLCDClient();
const accounts = getMnemonics();
const wallet = LCD.chain1.wallet(accounts.smartaccPreTxMnemonic);
const smartaccAddress = accounts.smartaccPreTxMnemonic.accAddress("terra");
const receiver = accounts.smartaccControllerMnemonic.accAddress("terra")

const deployer = LCD.chain1.wallet(accounts.tokenFactoryMnemonic);
const deployerAddress = accounts.tokenFactoryMnemonic.accAddress("terra");

let limitContractAddress: string;

test('Create new smart account', async () => {
try {
// create the smartaccount
let tx = await wallet.createAndSignTx({
msgs: [new MsgCreateSmartAccount(
smartaccAddress
)],
chainID: 'test-1',
gas: '400000',
});
await LCD.chain1.tx.broadcastSync(tx, "test-1");
await blockInclusion();
// Query smartaccount setting for the smart waccount
let setting = await LCD.chain1.smartaccount.setting(smartaccAddress);
expect(setting.toData())
.toEqual({
owner: smartaccAddress,
authorization: [],
post_transaction: [],
pre_transaction: [],
fallback: true,
});
} catch (e:any) {
console.log(e);
expect(e).toBeUndefined();
}
});

test('Deploy smart account limit contract', async () => {
try {
let tx = await deployer.createAndSignTx({
msgs: [new MsgStoreCode(
deployerAddress,
fs.readFileSync(path.join(__dirname, "/../../../../x/smartaccount/test_helpers/test_data/limit_min_coins_hooks.wasm")).toString("base64"),
)],
chainID: "test-1",
});

let result = await LCD.chain1.tx.broadcastSync(tx, "test-1");
await blockInclusion();
let txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any;
let codeId = Number(txResult.logs[0].events[1].attributes[1].value);
expect(codeId).toBeDefined();

const msgInstantiateContract = new MsgInstantiateContract(
deployerAddress,
deployerAddress,
codeId,
{},
Coins.fromString("1uluna"),
"limit contract " + Math.random(),
);

tx = await deployer.createAndSignTx({
msgs: [msgInstantiateContract],
chainID: "test-1",
});
result = await LCD.chain1.tx.broadcastSync(tx, "test-1");
await blockInclusion();
txResult = await LCD.chain1.tx.txInfo(result.txhash, "test-1") as any;
limitContractAddress = txResult.logs[0].events[4].attributes[0].value;
expect(limitContractAddress).toBeDefined();
} catch(e: any) {
console.log(e)
expect(e).toBeUndefined();
}
});

test('Update post tx hooks', async () => {
try {
// signing with the controlledAccountAddress should now fail
let tx = await wallet.createAndSignTx({
msgs: [
new MsgUpdateTransactionHooks(
smartaccAddress,
[],
[limitContractAddress],
),
],
chainID: "test-1",
gas: '400000',
});
await LCD.chain1.tx.broadcastSync(tx, "test-1");
await blockInclusion();

// check if update authorization was successful
let setting = await LCD.chain1.smartaccount.setting(smartaccAddress);
expect(setting.postTransaction).toEqual([limitContractAddress]);
} catch (e:any) {
console.log(e)
expect(e).toBeUndefined();
}
});

test('Transaction should pass for sending below limit', async () => {
try {
let setting = await LCD.chain1.smartaccount.setting(smartaccAddress);
expect(setting.postTransaction).toEqual([limitContractAddress]);

const balance = await LCD.chain1.bank.balance(smartaccAddress);
const coinsToSend = balance[0].sub(Coins.fromString("1000000uluna"));

const receiverBalanceBefore = await LCD.chain1.bank.balance(receiver);

let tx = await wallet.createAndSignTx({
msgs: [
new MsgSend(
smartaccAddress,
receiver,
coinsToSend,
),
],
chainID: "test-1",
gas: '400000',
});
// expect.assertions(1);
await LCD.chain1.tx.broadcastSync(tx, "test-1");
await blockInclusion();

// check that MsgSend succeeds
const receiverBalanceAfter = await LCD.chain1.bank.balance(receiver);
const deltaBalance = receiverBalanceAfter[0].sub(receiverBalanceBefore[0]);
expect(deltaBalance).toEqual(coinsToSend);
} catch (e:any) {
console.log(e)
expect(e).toBeUndefined();
}
});

test('Transaction should fail for sending over limit', async () => {
try {
let setting = await LCD.chain1.smartaccount.setting(smartaccAddress);
expect(setting.postTransaction).toEqual([limitContractAddress]);

// should have 940000uluna at this point 60000
const balanceBefore = await LCD.chain1.bank.balance(smartaccAddress);
// leave 23905uluna for fees so transaction will not fail due to insufficient funds
// should cost 23705uluna
const coinsToSend = balanceBefore[0].sub(Coins.fromString("23905uluna"));

const coinBefore = balanceBefore[0].find((coin: Coin) => coin.denom === "uluna");
expect(coinBefore).toBeDefined();

let tx = await wallet.createAndSignTx({
msgs: [
new MsgSend(
smartaccAddress,
receiver,
coinsToSend,
),
],
chainID: "test-1",
});
const fee_coins = tx.toData().auth_info.fee.amount;

// fee_coins[0].amount string to number
const fee_amount = parseInt(fee_coins[0].amount);

await LCD.chain1.tx.broadcastSync(tx, "test-1");
await blockInclusion();

// check that MsgSend failed
const balanceAfter = await LCD.chain1.bank.balance(smartaccAddress);
const coinAfter = balanceAfter[0].find((coin: Coin) => coin.denom === "uluna");
expect(coinAfter).toBeDefined();

// check that only fees were deducted
const coinAfter_amount = parseInt(coinAfter!.amount.toString());
const balaceBeforeMinusFee = coinBefore!.amount.toNumber() - fee_amount;
expect(balaceBeforeMinusFee).toEqual(coinAfter_amount);
} catch (e:any) {
console.log(e)
expect(e).toBeUndefined();
}
});
});
9 changes: 3 additions & 6 deletions integration-tests/src/modules/smartaccount/pretx.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,13 @@ describe("Smartaccount Module (https://github.com/terra-money/core/tree/release/
// Prepare environment clients, accounts and wallets
const LCD = getLCDClient();
const accounts = getMnemonics();
const wallet = LCD.chain1.wallet(accounts.mnemonic3);
const controlledAccountAddress = accounts.mnemonic3.accAddress("terra");

const pubkey = accounts.mnemonic4.publicKey;
expect(pubkey).toBeDefined();
const wallet = LCD.chain1.wallet(accounts.smartaccPostTxMnemonic);
const controlledAccountAddress = accounts.smartaccPostTxMnemonic.accAddress("terra");

const deployer = LCD.chain1.wallet(accounts.tokenFactoryMnemonic);
const deployerAddress = accounts.tokenFactoryMnemonic.accAddress("terra");

let limitContractAddress: string = "terra1nc5tatafv6eyq7llkr2gv50ff9e22mnf70qgjlv737ktmt4eswrquka9l6"
let limitContractAddress: string;

test('Create new smart account', async () => {
try {
Expand Down
26 changes: 17 additions & 9 deletions x/smartaccount/post/posttransaction_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ func (s *PostTxTestSuite) TestPostTransactionHookWithEmptySmartAccount() {

func (s *PostTxTestSuite) TestInvalidContractAddress() {
s.Setup()
s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{
s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, &smartaccounttypes.Setting{
PostTransaction: []string{s.TestAccs[0].String()},
})
txBuilder := s.BuildDefaultMsgTx(0, &types.MsgSend{
Expand All @@ -74,46 +74,54 @@ func (s *PostTxTestSuite) TestInvalidContractAddress() {
require.ErrorContainsf(s.T(), err, "no such contract", "error message: %s", err)
}

func (s *PostTxTestSuite) TestSendCoinsWithLimitSendHook() {
func (s *PostTxTestSuite) TestSendWithinLimitWithLimitCoinsSendHook() {
s.Setup()

acc := s.TestAccs[0]
codeId, _, err := s.WasmKeeper.Create(s.Ctx, acc, test_helpers.LimitSendOnlyHookWasm, nil)
codeId, _, err := s.WasmKeeper.Create(s.Ctx, acc, test_helpers.LimitMinCoinsHookWasm, nil)
require.NoError(s.T(), err)
contractAddr, _, err := s.WasmKeeper.Instantiate(s.Ctx, codeId, acc, acc, []byte("{}"), "limit send", sdk.NewCoins())
require.NoError(s.T(), err)

s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{
s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, &smartaccounttypes.Setting{
PostTransaction: []string{contractAddr.String()},
})

err = s.App.Keepers.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, acc, smartaccounttypes.ModuleName, sdk.NewCoins(sdk.NewInt64Coin("uluna", 10000000)))
require.NoError(s.T(), err)

txBuilder := s.BuildDefaultMsgTx(0, &types.MsgSend{
FromAddress: acc.String(),
ToAddress: acc.String(),
Amount: sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)),
Amount: sdk.NewCoins(sdk.NewInt64Coin("uluna", 10000000)),
})
_, err = s.PostTxDecorator.PostHandle(s.Ctx, txBuilder.GetTx(), false, true, sdk.ChainPostDecorators(sdk.Terminator{}))
require.NoError(s.T(), err)
}

func (s *PostTxTestSuite) TestStakingWithLimitSendHook() {
func (s *PostTxTestSuite) TestSendOverLimitWithLimitCoinsSendHook() {
s.Setup()

acc := s.TestAccs[0]
codeId, _, err := s.WasmKeeper.Create(s.Ctx, acc, test_helpers.LimitSendOnlyHookWasm, nil)
codeId, _, err := s.WasmKeeper.Create(s.Ctx, acc, test_helpers.LimitMinCoinsHookWasm, nil)
require.NoError(s.T(), err)
contractAddr, _, err := s.WasmKeeper.Instantiate(s.Ctx, codeId, acc, acc, []byte("{}"), "limit send", sdk.NewCoins())
require.NoError(s.T(), err)

s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, smartaccounttypes.Setting{
s.Ctx = s.Ctx.WithValue(smartaccounttypes.ModuleName, &smartaccounttypes.Setting{
PostTransaction: []string{contractAddr.String()},
})

err = s.App.Keepers.BankKeeper.SendCoinsFromAccountToModule(s.Ctx, acc, smartaccounttypes.ModuleName, sdk.NewCoins(sdk.NewInt64Coin("uluna", 100000000)))
require.NoError(s.T(), err)

txBuilder := s.BuildDefaultMsgTx(0, &stakingtypes.MsgDelegate{
DelegatorAddress: acc.String(),
ValidatorAddress: acc.String(),
Amount: sdk.NewInt64Coin("uluna", 100000000),
})
_, err = s.PostTxDecorator.PostHandle(s.Ctx, txBuilder.GetTx(), false, true, sdk.ChainPostDecorators(sdk.Terminator{}))
require.ErrorContainsf(s.T(), err, "Unauthorized message type", "error message: %s", err)
require.ErrorContainsf(s.T(), err, "Failed post transaction process", "error message: %s", err)
}

func (s *PostTxTestSuite) BuildDefaultMsgTx(accountIndex int, msgs ...sdk.Msg) client.TxBuilder {
Expand Down
3 changes: 3 additions & 0 deletions x/smartaccount/test_helpers/test_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,6 @@ var SmartAuthContractWasm []byte

//go:embed test_data/smart_auth_multisig.wasm
var SmartMultiSigWasm []byte

//go:embed test_data/limit_min_coins_hooks.wasm
var LimitMinCoinsHookWasm []byte
Binary file not shown.

0 comments on commit f764d50

Please sign in to comment.