Handle bad account userdata better
This commit is contained in:
parent
8f01f7cf21
commit
5691bf557c
|
@ -19,6 +19,7 @@ pub enum BudgetError {
|
||||||
NegativeTokens,
|
NegativeTokens,
|
||||||
DestinationMissing(Pubkey),
|
DestinationMissing(Pubkey),
|
||||||
FailedWitness,
|
FailedWitness,
|
||||||
|
UserdataTooSmall,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
|
#[derive(Serialize, Deserialize, Debug, Clone, Default, PartialEq)]
|
||||||
|
@ -148,13 +149,12 @@ impl BudgetContract {
|
||||||
state.pending_budget = Some(budget);
|
state.pending_budget = Some(budget);
|
||||||
accounts[1].tokens += contract.tokens;
|
accounts[1].tokens += contract.tokens;
|
||||||
state.initialized = true;
|
state.initialized = true;
|
||||||
state.serialize(&mut accounts[1].userdata);
|
state.serialize(&mut accounts[1].userdata)
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::ApplyTimestamp(dt) => {
|
Instruction::ApplyTimestamp(dt) => {
|
||||||
let mut state = Self::deserialize(&accounts[1].userdata).unwrap();
|
if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
|
||||||
if !state.is_pending() {
|
if !state.is_pending() {
|
||||||
Err(BudgetError::ContractNotPending(tx.keys[1]))
|
Err(BudgetError::ContractNotPending(tx.keys[1]))
|
||||||
} else if !state.initialized {
|
} else if !state.initialized {
|
||||||
|
@ -164,12 +164,14 @@ impl BudgetContract {
|
||||||
trace!("apply timestamp");
|
trace!("apply timestamp");
|
||||||
state.apply_timestamp(&tx.keys, accounts, *dt)?;
|
state.apply_timestamp(&tx.keys, accounts, *dt)?;
|
||||||
trace!("apply timestamp committed");
|
trace!("apply timestamp committed");
|
||||||
state.serialize(&mut accounts[1].userdata);
|
state.serialize(&mut accounts[1].userdata)
|
||||||
Ok(())
|
}
|
||||||
|
} else {
|
||||||
|
Err(BudgetError::UninitializedContract(tx.keys[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::ApplySignature => {
|
Instruction::ApplySignature => {
|
||||||
let mut state = Self::deserialize(&accounts[1].userdata).unwrap();
|
if let Ok(mut state) = Self::deserialize(&accounts[1].userdata) {
|
||||||
if !state.is_pending() {
|
if !state.is_pending() {
|
||||||
Err(BudgetError::ContractNotPending(tx.keys[1]))
|
Err(BudgetError::ContractNotPending(tx.keys[1]))
|
||||||
} else if !state.initialized {
|
} else if !state.initialized {
|
||||||
|
@ -179,8 +181,10 @@ impl BudgetContract {
|
||||||
trace!("apply signature");
|
trace!("apply signature");
|
||||||
state.apply_signature(&tx.keys, accounts)?;
|
state.apply_signature(&tx.keys, accounts)?;
|
||||||
trace!("apply signature committed");
|
trace!("apply signature committed");
|
||||||
state.serialize(&mut accounts[1].userdata);
|
state.serialize(&mut accounts[1].userdata)
|
||||||
Ok(())
|
}
|
||||||
|
} else {
|
||||||
|
Err(BudgetError::UninitializedContract(tx.keys[1]))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Instruction::NewVote(_vote) => {
|
Instruction::NewVote(_vote) => {
|
||||||
|
@ -190,16 +194,26 @@ impl BudgetContract {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn serialize(&self, output: &mut [u8]) {
|
fn serialize(&self, output: &mut [u8]) -> Result<(), BudgetError> {
|
||||||
let len = serialized_size(self).unwrap() as u64;
|
let len = serialized_size(self).unwrap() as u64;
|
||||||
|
if output.len() < len as usize {
|
||||||
|
warn!(
|
||||||
|
"{} bytes required to serialize, only have {} bytes",
|
||||||
|
len,
|
||||||
|
output.len()
|
||||||
|
);
|
||||||
|
return Err(BudgetError::UserdataTooSmall);
|
||||||
|
}
|
||||||
{
|
{
|
||||||
let writer = io::BufWriter::new(&mut output[..8]);
|
let writer = io::BufWriter::new(&mut output[..8]);
|
||||||
serialize_into(writer, &len).unwrap();
|
serialize_into(writer, &len).unwrap();
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
let writer = io::BufWriter::new(&mut output[8..8 + len as usize]);
|
let writer = io::BufWriter::new(&mut output[8..8 + len as usize]);
|
||||||
serialize_into(writer, self).unwrap();
|
serialize_into(writer, self).unwrap();
|
||||||
}
|
}
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn deserialize(input: &[u8]) -> bincode::Result<Self> {
|
pub fn deserialize(input: &[u8]) -> bincode::Result<Self> {
|
||||||
|
@ -220,7 +234,7 @@ impl BudgetContract {
|
||||||
if let Ok(mut state) = BudgetContract::deserialize(&accounts[1].userdata) {
|
if let Ok(mut state) = BudgetContract::deserialize(&accounts[1].userdata) {
|
||||||
trace!("saved error {:?}", e);
|
trace!("saved error {:?}", e);
|
||||||
state.last_error = Some(e);
|
state.last_error = Some(e);
|
||||||
state.serialize(&mut accounts[1].userdata);
|
state.serialize(&mut accounts[1].userdata).unwrap();
|
||||||
} else {
|
} else {
|
||||||
trace!("error in uninitialized contract {:?}", e,);
|
trace!("error in uninitialized contract {:?}", e,);
|
||||||
}
|
}
|
||||||
|
@ -233,6 +247,7 @@ impl BudgetContract {
|
||||||
/// be spent from this account .
|
/// be spent from this account .
|
||||||
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> () {
|
pub fn process_transaction(tx: &Transaction, accounts: &mut [Account]) -> () {
|
||||||
let instruction = deserialize(&tx.userdata).unwrap();
|
let instruction = deserialize(&tx.userdata).unwrap();
|
||||||
|
trace!("process_transaction: {:?}", instruction);
|
||||||
let _ = Self::apply_debits_to_budget_state(tx, accounts, &instruction)
|
let _ = Self::apply_debits_to_budget_state(tx, accounts, &instruction)
|
||||||
.and_then(|_| Self::apply_credits_to_budget_state(tx, accounts, &instruction))
|
.and_then(|_| Self::apply_credits_to_budget_state(tx, accounts, &instruction))
|
||||||
.map_err(|e| {
|
.map_err(|e| {
|
||||||
|
@ -268,13 +283,23 @@ mod test {
|
||||||
fn test_serializer() {
|
fn test_serializer() {
|
||||||
let mut a = Account::new(0, 512, BudgetContract::id());
|
let mut a = Account::new(0, 512, BudgetContract::id());
|
||||||
let b = BudgetContract::default();
|
let b = BudgetContract::default();
|
||||||
b.serialize(&mut a.userdata);
|
b.serialize(&mut a.userdata).unwrap();
|
||||||
let buf = serialize(&b).unwrap();
|
let buf = serialize(&b).unwrap();
|
||||||
assert_eq!(a.userdata[8..8 + buf.len()], buf[0..]);
|
assert_eq!(a.userdata[8..8 + buf.len()], buf[0..]);
|
||||||
let c = BudgetContract::deserialize(&a.userdata).unwrap();
|
let c = BudgetContract::deserialize(&a.userdata).unwrap();
|
||||||
assert_eq!(b, c);
|
assert_eq!(b, c);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_serializer_userdata_too_small() {
|
||||||
|
let mut a = Account::new(0, 1, BudgetContract::id());
|
||||||
|
let b = BudgetContract::default();
|
||||||
|
assert_eq!(
|
||||||
|
b.serialize(&mut a.userdata),
|
||||||
|
Err(BudgetError::UserdataTooSmall)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn test_transfer_on_date() {
|
fn test_transfer_on_date() {
|
||||||
let mut accounts = vec![
|
let mut accounts = vec![
|
||||||
|
@ -425,6 +450,41 @@ mod test {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_userdata_too_small() {
|
||||||
|
let mut accounts = vec![
|
||||||
|
Account::new(1, 0, BudgetContract::id()),
|
||||||
|
Account::new(1, 0, BudgetContract::id()), // <== userdata is 0, which is not enough
|
||||||
|
Account::new(1, 0, BudgetContract::id()),
|
||||||
|
];
|
||||||
|
let from = Keypair::new();
|
||||||
|
let contract = Keypair::new();
|
||||||
|
let to = Keypair::new();
|
||||||
|
let tx = Transaction::budget_new_on_date(
|
||||||
|
&from,
|
||||||
|
to.pubkey(),
|
||||||
|
contract.pubkey(),
|
||||||
|
Utc::now(),
|
||||||
|
1,
|
||||||
|
Hash::default(),
|
||||||
|
);
|
||||||
|
|
||||||
|
BudgetContract::process_transaction(&tx, &mut accounts);
|
||||||
|
assert!(BudgetContract::deserialize(&accounts[1].userdata).is_err());
|
||||||
|
|
||||||
|
let tx = Transaction::budget_new_timestamp(
|
||||||
|
&from,
|
||||||
|
contract.pubkey(),
|
||||||
|
to.pubkey(),
|
||||||
|
Utc::now(),
|
||||||
|
Hash::default(),
|
||||||
|
);
|
||||||
|
BudgetContract::process_transaction(&tx, &mut accounts);
|
||||||
|
assert!(BudgetContract::deserialize(&accounts[1].userdata).is_err());
|
||||||
|
|
||||||
|
// Success if there was no panic...
|
||||||
|
}
|
||||||
|
|
||||||
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
/// Detect binary changes in the serialized contract userdata, which could have a downstream
|
||||||
/// affect on SDKs and DApps
|
/// affect on SDKs and DApps
|
||||||
#[test]
|
#[test]
|
||||||
|
|
Loading…
Reference in New Issue