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

Fix: close remainer to not remove auth account for rekeyed account #575

20 changes: 19 additions & 1 deletion packages/runtime/src/ctx.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,7 @@ export class Ctx implements Context {
this.assertAccBalAboveMin(fromAccount.address);

if (txParam.payFlags.closeRemainderTo) {
this.verifyCloseRemainderTo(txParam);
const closeRemToAcc = this.getAccount(txParam.payFlags.closeRemainderTo);

closeRemToAcc.amount += fromAccount.amount; // transfer funds of sender to closeRemTo account
Expand Down Expand Up @@ -374,6 +375,17 @@ export class Ctx implements Context {
}
}

/**
* Verify closeRemainderTo field is different with fromAccountAddr
* @param txParam transaction param
*/
verifyCloseRemainderTo (txParam: types.ExecParams): void {
if (!txParam.payFlags.closeRemainderTo) return;
if (txParam.payFlags.closeRemainderTo === webTx.getFromAddress(txParam)) {
throw new RuntimeError(RUNTIME_ERRORS.GENERAL.INVALID_CLOSE_REMAINDER_TO);
}
}

/**
* Deduct transaction fee from sender account.
* @param sender Sender address
Expand Down Expand Up @@ -413,6 +425,8 @@ export class Ctx implements Context {
toAssetHolding.amount += BigInt(txParam.amount);

if (txParam.payFlags.closeRemainderTo) {
this.verifyCloseRemainderTo(txParam);

const closeToAddr = txParam.payFlags.closeRemainderTo;
if (fromAccountAddr === fromAssetHolding.creator) {
throw new RuntimeError(RUNTIME_ERRORS.ASA.CANNOT_CLOSE_ASSET_BY_CREATOR);
Expand Down Expand Up @@ -749,7 +763,11 @@ export class Ctx implements Context {
break;
}
}

// if closeRemainderTo field occur in txParam
// we will change rekeyTo field to webTx.getFromAddress(txParam)
if (txParam.payFlags.closeRemainderTo) {
txParam.payFlags.rekeyTo = webTx.getFromAddress(txParam);
}
// apply rekey after pass all logic
this.rekeyTo(txParam);

Expand Down
6 changes: 6 additions & 0 deletions packages/runtime/src/errors/errors-list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,12 @@ const runtimeGeneralErrors = {
message: "Should have been authorized by %spend% but was actually authorized by %signer%",
title: "Invalid spend account.",
description: "Invalid spend account"
},
INVALID_CLOSE_REMAINDER_TO: {
number: 1507,
message: "Transaction cannot close account to its sender",
title: "Transaction cannot close account to its sender.",
description: "Transaction cannot close account to its sender"
}
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,11 @@ import { types } from "@algo-builder/web";
import { getApplicationAddress } from "algosdk";
import { assert } from "chai";

import { RUNTIME_ERRORS } from "../../src/errors/errors-list";
import { AccountStore, Runtime } from "../../src/index";
import { DeployedAppTxReceipt } from "../../src/types";
import { useFixture } from "../helpers/integration";
import { expectRuntimeError } from "../helpers/runtime-errors";
import { RUNTIME_ERRORS } from "../../../src/errors/errors-list";
import { AccountStore, Runtime } from "../../../src/index";
import { DeployedAppTxReceipt } from "../../../src/types";
import { useFixture } from "../../helpers/integration";
import { expectRuntimeError } from "../../helpers/runtime-errors";

// default initial balance
const baseBalance = 20e9;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
/* eslint-disable sonarjs/no-duplicate-string */
import { types } from "@algo-builder/web";
import { LogicSigAccount } from "algosdk";
import { assert } from "chai";

import { RUNTIME_ERRORS } from "../../src/errors/errors-list";
import { AccountStore, Runtime } from "../../src/index";
import { useFixture } from "../helpers/integration";
import { expectRuntimeError } from "../helpers/runtime-errors";
import { RUNTIME_ERRORS } from "../../../src/errors/errors-list";
import { AccountStore, Runtime } from "../../../src/index";
import { useFixture } from "../../helpers/integration";
import { expectRuntimeError } from "../../helpers/runtime-errors";

// default initial balance
const baseBalance = 20e9;
Expand Down Expand Up @@ -141,46 +142,63 @@ describe("Re-keying transactions", function () {
);
});

describe("Rekey an already rekeyed account", function () {
this.beforeEach(() => {
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.SecretKey,
fromAccount: bob.account,
fromAccountAddr: alice.address,
toAccountAddr: bob.address,
amountMicroAlgos: amount,
payFlags: { totalFee: fee, rekeyTo: lsigAccount.address }
};

runtime.executeTx(txParams);
syncAccounts();
});

it("Check spend key", function () {
assert.equal(alice.getSpendAddress(), lsigAccount.address);
});
it("Can rekey account again", () => {
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.SecretKey,
fromAccount: bob.account,
fromAccountAddr: alice.address,
toAccountAddr: bob.address,
amountMicroAlgos: amount,
payFlags: { totalFee: fee, rekeyTo: lsigAccount.address }
};

runtime.executeTx(txParams);
syncAccounts();
// check spend address
assert.equal(alice.getSpendAddress(), lsigAccount.address);
});

it("Can Rekey again back to orginal account", () => {
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.SecretKey,
fromAccount: bob.account,
fromAccountAddr: alice.address,
toAccountAddr: bob.address,
amountMicroAlgos: amount,
payFlags: { totalFee: fee, rekeyTo: alice.address }
};

runtime.executeTx(txParams);
syncAccounts();
// check spend address
assert.equal(alice.getSpendAddress(), alice.address);
});

describe("Rekey again back to orginal account", function () {
this.beforeEach(() => {
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.SecretKey,
fromAccount: bob.account,
fromAccountAddr: alice.address,
toAccountAddr: bob.address,
amountMicroAlgos: amount,
payFlags: { totalFee: fee, rekeyTo: alice.address }
};

runtime.executeTx(txParams);
syncAccounts();
});

it("Check spend key", function () {
assert.equal(alice.getSpendAddress(), alice.address);
});
it("close account should remove spend/auth address", () => {
const aliceBalanceBefore = alice.balance();
const bobBalanceBefore = bob.balance();

txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.SecretKey,
fromAccount: bob.account,
fromAccountAddr: alice.address,
toAccountAddr: bob.address,
amountMicroAlgos: 0,
payFlags: {
totalFee: fee,
closeRemainderTo: bob.address
}
};
runtime.executeTx(txParams);
syncAccounts();

// check account state after clsoe
assert.equal(alice.balance(), 0n);
assert.equal(bob.balance(), aliceBalanceBefore + bobBalanceBefore - BigInt(fee));
assert.equal(alice.getSpendAddress(), alice.address);
});
});

Expand Down Expand Up @@ -268,6 +286,33 @@ describe("Re-keying transactions", function () {
rekeyMessageError(alice.getSpendAddress(), john.address)
);
});

it("close account should remove spend/auth address", () => {
const aliceBalanceBefore = alice.balance();
const bobBalanceBefore = bob.balance();

// close alice account to bob
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.LogicSignature,
fromAccountAddr: alice.address,
toAccountAddr: bob.address,
amountMicroAlgos: 0n,
lsig: lsig,
payFlags: {
totalFee: fee,
closeRemainderTo: bob.address
}
};

runtime.executeTx(txParams);
syncAccounts();

// check account state after close
assert.equal(alice.balance(), 0n);
assert.equal(bob.balance(), aliceBalanceBefore + bobBalanceBefore - BigInt(fee));
assert.equal(alice.getSpendAddress(), alice.address);
});
});

describe("Lsig to Lsig", function () {
Expand Down Expand Up @@ -337,6 +382,33 @@ describe("Re-keying transactions", function () {
rekeyMessageError(lsigAccount.getSpendAddress(), lsigAccount.address)
);
});

it("close account should remove spend/auth address", () => {
const lsigAccountBalanceBefore = lsigAccount.balance();
const aliceBalanceBefore = alice.balance();

// close lsig account to alice
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.LogicSignature,
fromAccountAddr: lsigAccount.address,
toAccountAddr: bob.address,
amountMicroAlgos: 0n,
lsig: cloneLsig,
payFlags: {
totalFee: fee,
closeRemainderTo: alice.address
}
};

runtime.executeTx(txParams);
syncAccounts();

// check account state after close
assert.equal(lsigAccount.balance(), 0n);
assert.equal(alice.balance(), lsigAccountBalanceBefore + aliceBalanceBefore - BigInt(fee));
assert.equal(lsigAccount.getSpendAddress(), lsigAccount.address);
});
});

describe("Lsig to account", function () {
Expand Down Expand Up @@ -409,6 +481,33 @@ describe("Re-keying transactions", function () {

assert.equal(lsigAccount.getSpendAddress(), bob.address);
});

it("close account should remove auth/spend address", () => {
// balance before rekey
const lsigBalanceBefore = lsigAccount.balance();
const aliceBalanceBefore = alice.balance();
// transfer ALGO use lsig
txParams = {
type: types.TransactionType.TransferAlgo,
sign: types.SignType.SecretKey,
fromAccount: bob.account,
fromAccountAddr: lsigAccount.address,
toAccountAddr: alice.address,
amountMicroAlgos: 0n,
payFlags: {
totalFee: fee,
closeRemainderTo: alice.address
}
};

runtime.executeTx(txParams);

// check account state after close
syncAccounts();
assert.equal(lsigAccount.balance(), 0n);
assert.equal(alice.balance(), lsigBalanceBefore + aliceBalanceBefore - BigInt(fee));
assert.equal(lsigAccount.getSpendAddress(), lsigAccount.address);
});
});

describe("Rekey by another Tx type", function () {
Expand Down
Loading