const anchor = require("@project-serum/anchor");
const { PublicKey, SystemProgram, Transaction, TransactionInstruction } = anchor.web3;
const assert = require("assert");
describe("CEI Violation", () => {
const provider = anchor.AnchorProvider.env();
anchor.setProvider(provider);
const program = anchor.workspace.Rustfund;
it("Shows interactions before effects can lead to observable inconsistency", async () => {
const fundKp = anchor.web3.Keypair.generate();
const contributorKp = anchor.web3.Keypair.generate();
const [contributionPda] = await PublicKey.findProgramAddress(
[fundKp.publicKey.toBuffer(), contributorKp.publicKey.toBuffer()],
program.programId
);
await program.rpc.fundCreate("test", "desc", new anchor.BN(2000), {
accounts: {
fund: fundKp.publicKey,
creator: provider.wallet.publicKey,
systemProgram: SystemProgram.programId,
},
signers: [fundKp],
});
await provider.connection.requestAirdrop(fundKp.publicKey, 2000);
await program.rpc.contribute(new anchor.BN(1000), {
accounts: {
fund: fundKp.publicKey,
contributor: contributorKp.publicKey,
contribution: contributionPda,
systemProgram: SystemProgram.programId,
},
signers: [contributorKp],
});
const tx = new Transaction();
tx.add(
new TransactionInstruction({
keys: [
{ pubkey: fundKp.publicKey, isSigner: false, isWritable: true },
{ pubkey: contributorKp.publicKey, isSigner: true, isWritable: true },
{ pubkey: contributionPda, isSigner: false, isWritable: true },
{ pubkey: SystemProgram.programId, isSigner: false, isWritable: false },
],
programId: program.programId,
data: Buffer.from([0x03]),
}),
SystemProgram.transfer({
fromPubkey: contributorKp.publicKey,
toPubkey: contributorKp.publicKey,
lamports: 0,
})
);
await provider.sendAndConfirm(tx, [contributorKp]);
const contribution = await program.account.contribution.fetch(contributionPda);
const fundBalance = await provider.connection.getBalance(fundKp.publicKey);
const contributorBalance = await provider.connection.getBalance(contributorKp.publicKey);
assert.equal(contribution.amount.toNumber(), 0, "State updated last");
assert.equal(fundBalance, 1000, "Funds transferred");
assert.equal(contributorBalance, 1000, "Contributor received funds");
});
});
pub fn refund(ctx: Context<FundRefund>) -> Result<()> {
let fund = &mut ctx.accounts.fund;
let contribution = &mut ctx.accounts.contribution;
let contributor = &mut ctx.accounts.contributor;
if fund.deadline != 0 && (fund.deadline as i64) > Clock::get()?.unix_timestamp {
return Err(ErrorCode::DeadlineNotReached.into());
}
let amount = contribution.amount;
contribution.amount = 0;
**fund.to_account_info().try_borrow_mut_lamports()? =
fund.to_account_info().lamports()
.checked_sub(amount)
.ok_or(ProgramError::InsufficientFunds)?;
**contributor.to_account_info().try_borrow_mut_lamports()? =
contributor.to_account_info().lamports()
.checked_add(amount)
.ok_or(ErrorCode::CalculationOverflow)?;
Ok(())
}