Memory
Memory Write
Summary
The MemoryWrite instruction lets you write different types of data to a memory account. A memory account is a user owned account that can store arbitrary amounts of data and is meant to be used to assert on instruction-level delta changes in onchain state.
There are several instruction arguments that you can use to configure the MemoryWrite instruction:
source_account: The account whose data you want to write to memory (Ignored for DataValue and Clock write types).
write_offset: The offset in the source account data where you want to write. The account will resize (realloc) to whatever the offset + data length is calculated to be, so multiple write instructions can be combined without worrying about the length of the account.
write_type: The type of data you want to write to memory. The data can be account data, account info fields, data values, or clock fields.
memory_id: The memory account ID. One user can have 256 memory accounts, or even more if you find and utilize different off-curve bumps per id.
pub enum WriteType {
AccountData { offset: u16, data_length: u16 },
AccountInfoField(AccountInfoField),
DataValue(DataValue),
Clock(ClockField),
}
You can write data to a memory account in the following ways:
- AccountData: Write the target account data to a memory account.
- AccountInfoField: Write the target account info field to a memory account.
- DataValue: Write a data value to a memory account.
- Clock: Write sysvar clock fields to a memory account.
Example: Writing token account data to a memory account using AccountData write type
The following instruction writes the first 72 bytes of a token account to memory. The first 72 bytes being the Mint (Pubkey), Owner (Pubkey), and Amount (u64).
Writing token account data to memory
const [memory, memoryBump] = await findMemoryPda({
memoryId: 0,
payer: userPubkey,
})
const ix = getMemoryWriteInstruction({
payer: userPubkey,
sourceAccount: tokenAccountKey,
programId: LIGHTHOUSE_PROGRAM_ADDRESS,
memory,
memoryId: 0,
memoryBump: memoryBump,
writeOffset: 0,
writeType: {
__kind: 'AccountData',
offset: 0,
dataLength: 72,
},
})
Writing these fields to memory means we can now assert on the delta changes between the token account and the memory account.
Asserting on stored memory
const [memory, memoryBump] = await findMemoryPda({
memoryId: 0,
payer: userPubkey,
})
const ixs = [
getAssertAccountDeltaInstruction({
accountA: memory,
accountB: tokenAccountKey,
assertion: accountDeltaAssertion('Data', {
aOffset: 0,
bOffset: 0,
assertion: dataValueDeltaAssertion('Bytes', {
operator: ByteSliceOperator.Equal,
length: 64,
}),
}),
}),
getAssertAccountDeltaInstruction({
accountA: memory,
accountB: tokenAccountKey,
assertion: accountDeltaAssertion('Data', {
aOffset: 64,
bOffset: 64,
assertion: dataValueDeltaAssertion('U64', {
value: -50,
operator: IntegerOperator.GreaterThan,
}),
}),
}),
]
The following delta assertions are checking that nothing has changed in the first 64 bytes of the token account since writing to memory and that the amount (byte offset 64 to 72) has only decreased by 50.
Lastly, you can close the memory account to free up rent.
Closing memory account
const [memory, memoryBump] = await findMemoryPda({
memoryId: 0,
payer: userPubkey,
})
const ix = getMemoryCloseInstruction({
payer: userPubkey,
programId: LIGHTHOUSE_PROGRAM_ADDRESS,
memory,
memoryBump,
memoryId: 0,
})
Example: Writing account info to a memory account using AccountInfoField write type
The following example writes the account info fields of a token account to memory. The account info fields are DataLength, Executable, Owner, Lamports, RentEpoch, and Key.
Writing account info to memory
const [memory, memoryBump] = await findMemoryPda({
memoryId: 0,
payer: userPubkey,
})
const builderFn = (writeType: WriteType, offset: number) => {
return getMemoryWriteInstruction({
payer: userPubkey,
sourceAccount: testAccountKey,
programId: LIGHTHOUSE_PROGRAM_ADDRESS,
memory,
memoryId: 0,
memoryBump,
writeOffset: offset,
writeType,
})
}
const tx = await pipe(
createTransaction({ version: 0 }),
(tx) =>
appendTransactionInstructions(
[
builderFn(
{
__kind: 'AccountInfoField',
fields: [AccountInfoField.DataLength],
},
0
),
builderFn(
{
__kind: 'AccountInfoField',
fields: [AccountInfoField.Executable],
},
8
),
builderFn(
{
__kind: 'AccountInfoField',
fields: [AccountInfoField.Owner],
},
16
),
builderFn(
{
__kind: 'AccountInfoField',
fields: [AccountInfoField.Lamports],
},
48
),
builderFn(
{
__kind: 'AccountInfoField',
fields: [AccountInfoField.RentEpoch],
},
56
),
builderFn(
{
__kind: 'AccountInfoField',
fields: [AccountInfoField.Key],
},
64
),
],
tx
),
(tx) => setTransactionFeePayer(userPubkey, tx),
(tx) => setTransactionLifetimeUsingBlockhash(recentBlockhash, tx),
(tx) => signTransaction([user], tx)
)
Example: Writing data value to a memory account using DataValue write type
The following example writes a u128 and a pubkey to a memory account and then asserts on the written values using lighthouse assertions.
Writing data value to memory
const [memory, memoryBump] = await findMemoryPda({
memoryId: 0,
payer: userPubkey,
})
const tx = await pipe(
createTransaction({ version: 0 }),
(tx) =>
appendTransactionInstructions(
[
getMemoryWriteInstruction({
payer: userPubkey,
sourceAccount: LIGHTHOUSE_PROGRAM_ADDRESS,
programId: LIGHTHOUSE_PROGRAM_ADDRESS,
memory,
memoryId: 0,
memoryBump,
writeOffset: 0,
writeType: {
__kind: 'DataValue',
fields: [
{
__kind: 'U128',
fields: [BigInt('340282366920938463463374607431768211455')],
},
],
},
}),
getMemoryWriteInstruction({
payer: userPubkey,
sourceAccount: LIGHTHOUSE_PROGRAM_ADDRESS,
programId: LIGHTHOUSE_PROGRAM_ADDRESS,
memory,
memoryId: 0,
memoryBump,
writeOffset: 32,
writeType: {
__kind: 'DataValue',
fields: [
{
__kind: 'Pubkey',
fields: [someKey],
},
],
},
}),
getAssertAccountDataInstruction({
targetAccount: memory,
assertion: dataValueAssertion('U128', {
value: BigInt('340282366920938463463374607431768211455'),
operator: IntegerOperator.Equal,
}),
offset: 0,
}),
getAssertAccountDataInstruction({
targetAccount: memory,
assertion: dataValueAssertion('Pubkey', {
value: someKey,
operator: EquatableOperator.Equal,
}),
offset: 32,
}),
],
tx
),
(tx) => setTransactionFeePayer(userPubkey, tx),
(tx) => setTransactionLifetimeUsingBlockhash(recentBlockhash, tx),
(tx) => signTransaction([user], tx)
)
Example: Writing clock fields to a memory account using Clock write type
Writing clock fields to memory
const [memory, memoryBump] = await findMemoryPda({
memoryId: 4,
payer: userPubkey,
})
const tx = await pipe(
createTransaction({ version: 0 }),
(tx) =>
appendTransactionInstructions(
[
getMemoryWriteInstruction({
memory,
memoryId: 4, // You can write to multiple memory accounts in a single transaction
memoryBump,
programId: LIGHTHOUSE_PROGRAM_ADDRESS,
payer: userPubkey,
sourceAccount: LIGHTHOUSE_PROGRAM_ADDRESS, // This account is ignored so should be an account already in the transaction to save transaction space.
writeOffset: 0,
writeType: {
__kind: 'Clock',
fields: [ClockField.Slot],
},
}),
],
tx
),
(tx) => setTransactionFeePayer(userPubkey, tx),
(tx) => setTransactionLifetimeUsingBlockhash(recentBlockhash, tx),
(tx) => signTransaction([user], tx)
)