fix: Squash budget bugs

This commit is contained in:
Tyera Eulberg 2019-05-24 16:07:16 -06:00 committed by Michael Vines
parent c99d8dd15c
commit 87c1becbef
6 changed files with 155 additions and 157 deletions

View File

@ -9,7 +9,6 @@ const solanaWeb3 = require('..');
const account1 = new solanaWeb3.Account(); const account1 = new solanaWeb3.Account();
const account2 = new solanaWeb3.Account(); const account2 = new solanaWeb3.Account();
const contractFunds = new solanaWeb3.Account();
const contractState = new solanaWeb3.Account(); const contractState = new solanaWeb3.Account();
let url; let url;
@ -23,28 +22,20 @@ function showBalance() {
return Promise.all([ return Promise.all([
connection.getBalance(account1.publicKey), connection.getBalance(account1.publicKey),
connection.getBalance(account2.publicKey), connection.getBalance(account2.publicKey),
connection.getBalance(contractFunds.publicKey),
connection.getBalance(contractState.publicKey), connection.getBalance(contractState.publicKey),
]).then( ]).then(([fromBalance, toBalance, contractStateBalance]) => {
([fromBalance, toBalance, contractFundsBalance, contractStateBalance]) => { console.log(
console.log( `Account1: ${account1.publicKey} has a balance of ${fromBalance}`,
`Account1: ${account1.publicKey} has a balance of ${fromBalance}`, );
); console.log(
console.log( `Account2: ${account2.publicKey} has a balance of ${toBalance}`,
`Account2: ${account2.publicKey} has a balance of ${toBalance}`, );
); console.log(
console.log( `Contract State: ${
`Contract Funds: ${ contractState.publicKey
contractFunds.publicKey } has a balance of ${contractStateBalance}`,
} has a balance of ${contractFundsBalance}`, );
); });
console.log(
`Contract State: ${
contractState.publicKey
} has a balance of ${contractStateBalance}`,
);
},
);
} }
function confirmTransaction(signature) { function confirmTransaction(signature) {
@ -52,6 +43,10 @@ function confirmTransaction(signature) {
return connection.getSignatureStatus(signature).then(confirmation => { return connection.getSignatureStatus(signature).then(confirmation => {
if (confirmation && 'Ok' in confirmation) { if (confirmation && 'Ok' in confirmation) {
console.log('Transaction confirmed'); console.log('Transaction confirmed');
} else if (confirmation) {
throw new Error(
`Transaction was not confirmed (${JSON.stringify(confirmation.Err)})`,
);
} else { } else {
throw new Error(`Transaction was not confirmed (${confirmation})`); throw new Error(`Transaction was not confirmed (${confirmation})`);
} }
@ -68,36 +63,10 @@ function airDrop() {
showBalance() showBalance()
.then(airDrop) .then(airDrop)
.then(showBalance) .then(showBalance)
.then(() => {
console.log(`\n== Creating account for the contract funds`);
const transaction = solanaWeb3.SystemProgram.createAccount(
account1.publicKey,
contractFunds.publicKey,
50, // number of lamports to transfer
0,
solanaWeb3.BudgetProgram.programId,
);
return connection.sendTransaction(transaction, account1);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => {
console.log(`\n== Creating account for the contract state`);
const transaction = solanaWeb3.SystemProgram.createAccount(
account1.publicKey,
contractState.publicKey,
1, // account1 pays 1 lamport to hold the contract state
solanaWeb3.BudgetProgram.space,
solanaWeb3.BudgetProgram.programId,
);
return connection.sendTransaction(transaction, account1);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => { .then(() => {
console.log(`\n== Initializing contract`); console.log(`\n== Initializing contract`);
const transaction = solanaWeb3.BudgetProgram.pay( const transaction = solanaWeb3.BudgetProgram.pay(
contractFunds.publicKey, account1.publicKey,
contractState.publicKey, contractState.publicKey,
account2.publicKey, account2.publicKey,
50, 50,
@ -106,7 +75,11 @@ showBalance()
new Date('2050'), new Date('2050'),
), ),
); );
return connection.sendTransaction(transaction, contractFunds); return solanaWeb3.sendAndConfirmTransaction(
connection,
transaction,
account1,
);
}) })
.then(confirmTransaction) .then(confirmTransaction)
.then(showBalance) .then(showBalance)
@ -118,7 +91,11 @@ showBalance()
account2.publicKey, account2.publicKey,
new Date('2050'), new Date('2050'),
); );
return connection.sendTransaction(transaction, account1); return solanaWeb3.sendAndConfirmTransaction(
connection,
transaction,
account1,
);
}) })
.then(confirmTransaction) .then(confirmTransaction)
.then(showBalance) .then(showBalance)

View File

@ -8,7 +8,6 @@ const solanaWeb3 = require('..');
const account1 = new solanaWeb3.Account(); const account1 = new solanaWeb3.Account();
const account2 = new solanaWeb3.Account(); const account2 = new solanaWeb3.Account();
const contractFunds = new solanaWeb3.Account();
const contractState = new solanaWeb3.Account(); const contractState = new solanaWeb3.Account();
const approver1 = new solanaWeb3.Account(); const approver1 = new solanaWeb3.Account();
@ -24,37 +23,34 @@ function showBalance() {
return Promise.all([ return Promise.all([
connection.getBalance(account1.publicKey), connection.getBalance(account1.publicKey),
connection.getBalance(account2.publicKey), connection.getBalance(account2.publicKey),
connection.getBalance(contractFunds.publicKey),
connection.getBalance(contractState.publicKey), connection.getBalance(contractState.publicKey),
]).then( ]).then(([fromBalance, toBalance, contractStateBalance]) => {
([fromBalance, toBalance, contractFundsBalance, contractStateBalance]) => { console.log(
console.log( `Account1: ${account1.publicKey} has a balance of ${fromBalance}`,
`Account1: ${account1.publicKey} has a balance of ${fromBalance}`, );
); console.log(
console.log( `Account2: ${account2.publicKey} has a balance of ${toBalance}`,
`Account2: ${account2.publicKey} has a balance of ${toBalance}`, );
); console.log(
console.log( `Contract State: ${
`Contract Funds: ${ contractState.publicKey
contractFunds.publicKey } has a balance of ${contractStateBalance}`,
} has a balance of ${contractFundsBalance}`, );
); });
console.log(
`Contract State: ${
contractState.publicKey
} has a balance of ${contractStateBalance}`,
);
},
);
} }
function confirmTransaction(signature) { function confirmTransaction(signature) {
console.log('Confirming transaction:', signature); console.log('Confirming transaction:', signature);
return connection.getSignatureStatus(signature).then(confirmation => { return connection.getSignatureStatus(signature).then(confirmation => {
if (confirmation !== 'Confirmed') { if (confirmation && 'Ok' in confirmation) {
console.log('Transaction confirmed');
} else if (confirmation) {
throw new Error(
`Transaction was not confirmed (${JSON.stringify(confirmation.Err)})`,
);
} else {
throw new Error(`Transaction was not confirmed (${confirmation})`); throw new Error(`Transaction was not confirmed (${confirmation})`);
} }
console.log('Transaction confirmed');
}); });
} }
@ -88,43 +84,21 @@ showBalance()
}) })
.then(confirmTransaction) .then(confirmTransaction)
.then(showBalance) .then(showBalance)
.then(() => {
console.log(`\n== Creating account for the contract funds`);
const transaction = solanaWeb3.SystemProgram.createAccount(
account1.publicKey,
contractFunds.publicKey,
50, // number of lamports to transfer
0,
solanaWeb3.BudgetProgram.programId,
);
return connection.sendTransaction(transaction, account1);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => {
console.log(`\n== Creating account for the contract state`);
const transaction = solanaWeb3.SystemProgram.createAccount(
account1.publicKey,
contractState.publicKey,
1, // account1 pays 1 lamport to hold the contract state
solanaWeb3.BudgetProgram.space,
solanaWeb3.BudgetProgram.programId,
);
return connection.sendTransaction(transaction, account1);
})
.then(confirmTransaction)
.then(showBalance)
.then(() => { .then(() => {
console.log(`\n== Initializing contract`); console.log(`\n== Initializing contract`);
const transaction = solanaWeb3.BudgetProgram.payOnBoth( const transaction = solanaWeb3.BudgetProgram.payOnBoth(
contractFunds.publicKey, account1.publicKey,
contractState.publicKey, contractState.publicKey,
account2.publicKey, account2.publicKey,
50, 50,
solanaWeb3.BudgetProgram.signatureCondition(approver1.publicKey), solanaWeb3.BudgetProgram.signatureCondition(approver1.publicKey),
solanaWeb3.BudgetProgram.signatureCondition(approver2.publicKey), solanaWeb3.BudgetProgram.signatureCondition(approver2.publicKey),
); );
return connection.sendTransaction(transaction, contractFunds); return solanaWeb3.sendAndConfirmTransaction(
connection,
transaction,
account1,
);
}) })
.then(confirmTransaction) .then(confirmTransaction)
.then(showBalance) .then(showBalance)
@ -135,7 +109,11 @@ showBalance()
contractState.publicKey, contractState.publicKey,
account2.publicKey, account2.publicKey,
); );
return connection.sendTransaction(transaction, approver1); return solanaWeb3.sendAndConfirmTransaction(
connection,
transaction,
approver1,
);
}) })
.then(confirmTransaction) .then(confirmTransaction)
.then(showBalance) .then(showBalance)
@ -146,7 +124,11 @@ showBalance()
contractState.publicKey, contractState.publicKey,
account2.publicKey, account2.publicKey,
); );
return connection.sendTransaction(transaction, approver2); return solanaWeb3.sendAndConfirmTransaction(
connection,
transaction,
approver2,
);
}) })
.then(confirmTransaction) .then(confirmTransaction)
.then(showBalance) .then(showBalance)

View File

@ -5851,7 +5851,7 @@
"dependencies": { "dependencies": {
"marked": { "marked": {
"version": "0.3.19", "version": "0.3.19",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
"dev": true "dev": true
}, },
@ -6115,7 +6115,7 @@
}, },
"marked": { "marked": {
"version": "0.3.19", "version": "0.3.19",
"resolved": "https://registry.npmjs.org/marked/-/marked-0.3.19.tgz", "resolved": "http://registry.npmjs.org/marked/-/marked-0.3.19.tgz",
"integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==", "integrity": "sha512-ea2eGWOqNxPcXv8dyERdSr/6FmzvWwzjMxpfGB/sbMccXoct+xY+YukPD+QTUZwyvK7BZwcr4m21WBOW41pAkg==",
"dev": true "dev": true
}, },
@ -12399,7 +12399,7 @@
}, },
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true "dev": true
}, },
@ -16878,7 +16878,7 @@
"dependencies": { "dependencies": {
"minimist": { "minimist": {
"version": "1.2.0", "version": "1.2.0",
"resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz",
"integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=",
"dev": true "dev": true
} }

View File

@ -4,7 +4,7 @@ import * as BufferLayout from 'buffer-layout';
import {Transaction} from './transaction'; import {Transaction} from './transaction';
import {PublicKey} from './publickey'; import {PublicKey} from './publickey';
import * as Layout from './layout'; import {SystemProgram} from './system-program';
/** /**
* Represents a condition that is met by executing a `applySignature()` * Represents a condition that is met by executing a `applySignature()`
@ -114,20 +114,10 @@ function serializeCondition(condition: BudgetCondition) {
return data; return data;
} }
case 'signature': { case 'signature': {
const dataLayout = BufferLayout.struct([
BufferLayout.u32('condition'),
Layout.publicKey('from'),
]);
const from = condition.from.toBuffer(); const from = condition.from.toBuffer();
const data = Buffer.alloc(4 + from.length); const data = Buffer.alloc(4 + from.length);
dataLayout.encode( data.writeUInt32LE(1, 0); // Condition enum = Signature
{ from.copy(data, 4);
instruction: 1, // Signature
from,
},
data,
);
return data; return data;
} }
default: default:
@ -190,8 +180,8 @@ export class BudgetProgram {
pos += 4; pos += 4;
switch (conditions.length) { switch (conditions.length) {
case 0: case 0: {
data.writeUInt32LE(0, pos); // Budget enum = Pay data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
pos += 4; pos += 4;
{ {
@ -199,17 +189,27 @@ export class BudgetProgram {
payment.copy(data, pos); payment.copy(data, pos);
pos += payment.length; pos += payment.length;
} }
const trimmedData = data.slice(0, pos);
return new Transaction().add({ const transaction = SystemProgram.createAccount(
from,
program,
amount,
trimmedData.length,
this.programId,
);
return transaction.add({
keys: [ keys: [
{pubkey: from, isSigner: true, isDebitable: true},
{pubkey: to, isSigner: false, isDebitable: false}, {pubkey: to, isSigner: false, isDebitable: false},
{pubkey: program, isSigner: false, isDebitable: true},
], ],
programId: this.programId, programId: this.programId,
data: data.slice(0, pos), data: trimmedData,
}); });
case 1: }
data.writeUInt32LE(1, pos); // Budget enum = After case 1: {
data.writeUInt32LE(1, pos); // BudgetExpr enum = After
pos += 4; pos += 4;
{ {
const condition = conditions[0]; const condition = conditions[0];
@ -218,23 +218,32 @@ export class BudgetProgram {
conditionData.copy(data, pos); conditionData.copy(data, pos);
pos += conditionData.length; pos += conditionData.length;
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
pos += 4;
const paymentData = serializePayment({amount, to}); const paymentData = serializePayment({amount, to});
paymentData.copy(data, pos); paymentData.copy(data, pos);
pos += paymentData.length; pos += paymentData.length;
} }
const trimmedData = data.slice(0, pos);
return new Transaction().add({ const transaction = SystemProgram.createAccount(
keys: [ from,
{pubkey: from, isSigner: true, isDebitable: true}, program,
{pubkey: program, isSigner: false, isDebitable: true}, amount,
{pubkey: to, isSigner: false, isDebitable: false}, trimmedData.length,
], this.programId,
);
return transaction.add({
keys: [{pubkey: program, isSigner: false, isDebitable: true}],
programId: this.programId, programId: this.programId,
data: data.slice(0, pos), data: trimmedData,
}); });
}
case 2: case 2: {
data.writeUInt32LE(2, pos); // Budget enum = Or data.writeUInt32LE(2, pos); // BudgetExpr enum = Or
pos += 4; pos += 4;
for (let condition of conditions) { for (let condition of conditions) {
@ -242,20 +251,29 @@ export class BudgetProgram {
conditionData.copy(data, pos); conditionData.copy(data, pos);
pos += conditionData.length; pos += conditionData.length;
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
pos += 4;
const paymentData = serializePayment({amount, to}); const paymentData = serializePayment({amount, to});
paymentData.copy(data, pos); paymentData.copy(data, pos);
pos += paymentData.length; pos += paymentData.length;
} }
const trimmedData = data.slice(0, pos);
return new Transaction().add({ const transaction = SystemProgram.createAccount(
keys: [ from,
{pubkey: from, isSigner: true, isDebitable: true}, program,
{pubkey: program, isSigner: false, isDebitable: true}, amount,
{pubkey: to, isSigner: false, isDebitable: false}, trimmedData.length,
], this.programId,
);
return transaction.add({
keys: [{pubkey: program, isSigner: false, isDebitable: true}],
programId: this.programId, programId: this.programId,
data: data.slice(0, pos), data: trimmedData,
}); });
}
default: default:
throw new Error( throw new Error(
@ -282,7 +300,7 @@ export class BudgetProgram {
data.writeUInt32LE(0, pos); // NewBudget instruction data.writeUInt32LE(0, pos); // NewBudget instruction
pos += 4; pos += 4;
data.writeUInt32LE(3, pos); // Budget enum = And data.writeUInt32LE(3, pos); // BudgetExpr enum = And
pos += 4; pos += 4;
for (let condition of [condition1, condition2]) { for (let condition of [condition1, condition2]) {
@ -291,18 +309,27 @@ export class BudgetProgram {
pos += conditionData.length; pos += conditionData.length;
} }
data.writeUInt32LE(0, pos); // BudgetExpr enum = Pay
pos += 4;
const paymentData = serializePayment({amount, to}); const paymentData = serializePayment({amount, to});
paymentData.copy(data, pos); paymentData.copy(data, pos);
pos += paymentData.length; pos += paymentData.length;
return new Transaction().add({ const trimmedData = data.slice(0, pos);
keys: [
{pubkey: from, isSigner: true, isDebitable: true}, const transaction = SystemProgram.createAccount(
{pubkey: program, isSigner: false, isDebitable: true}, from,
{pubkey: to, isSigner: false, isDebitable: false}, program,
], amount,
trimmedData.length,
this.programId,
);
return transaction.add({
keys: [{pubkey: program, isSigner: false, isDebitable: true}],
programId: this.programId, programId: this.programId,
data: data.slice(0, pos), data: trimmedData,
}); });
} }

View File

@ -164,6 +164,8 @@ export class Transaction {
let numCreditOnlySignedAccounts = 0; let numCreditOnlySignedAccounts = 0;
let numCreditOnlyUnsignedAccounts = 0; let numCreditOnlyUnsignedAccounts = 0;
const programIds = [];
this.instructions.forEach(instruction => { this.instructions.forEach(instruction => {
instruction.keys.forEach(keySignerPair => { instruction.keys.forEach(keySignerPair => {
const keyStr = keySignerPair.pubkey.toString(); const keyStr = keySignerPair.pubkey.toString();
@ -183,6 +185,12 @@ export class Transaction {
}); });
const programId = instruction.programId.toString(); const programId = instruction.programId.toString();
if (!programIds.includes(programId)) {
programIds.push(programId);
}
});
programIds.forEach(programId => {
if (!keys.includes(programId)) { if (!keys.includes(programId)) {
keys.push(programId); keys.push(programId);
numCreditOnlyUnsignedAccounts += 1; numCreditOnlyUnsignedAccounts += 1;

View File

@ -15,7 +15,8 @@ test('pay', () => {
to.publicKey, to.publicKey,
123, 123,
); );
expect(transaction.keys).toHaveLength(2); expect(transaction.instructions[0].keys).toHaveLength(2);
expect(transaction.instructions[1].keys).toHaveLength(2);
// TODO: Validate transaction contents more // TODO: Validate transaction contents more
transaction = BudgetProgram.pay( transaction = BudgetProgram.pay(
@ -25,7 +26,8 @@ test('pay', () => {
123, 123,
BudgetProgram.signatureCondition(from.publicKey), BudgetProgram.signatureCondition(from.publicKey),
); );
expect(transaction.keys).toHaveLength(3); expect(transaction.instructions[0].keys).toHaveLength(2);
expect(transaction.instructions[1].keys).toHaveLength(1);
// TODO: Validate transaction contents more // TODO: Validate transaction contents more
transaction = BudgetProgram.pay( transaction = BudgetProgram.pay(
@ -36,7 +38,8 @@ test('pay', () => {
BudgetProgram.signatureCondition(from.publicKey), BudgetProgram.signatureCondition(from.publicKey),
BudgetProgram.timestampCondition(from.publicKey, new Date()), BudgetProgram.timestampCondition(from.publicKey, new Date()),
); );
expect(transaction.keys).toHaveLength(3); expect(transaction.instructions[0].keys).toHaveLength(2);
expect(transaction.instructions[1].keys).toHaveLength(1);
// TODO: Validate transaction contents more // TODO: Validate transaction contents more
transaction = BudgetProgram.payOnBoth( transaction = BudgetProgram.payOnBoth(
@ -47,7 +50,8 @@ test('pay', () => {
BudgetProgram.signatureCondition(from.publicKey), BudgetProgram.signatureCondition(from.publicKey),
BudgetProgram.timestampCondition(from.publicKey, new Date()), BudgetProgram.timestampCondition(from.publicKey, new Date()),
); );
expect(transaction.keys).toHaveLength(3); expect(transaction.instructions[0].keys).toHaveLength(2);
expect(transaction.instructions[1].keys).toHaveLength(1);
// TODO: Validate transaction contents more // TODO: Validate transaction contents more
}); });