Skip to content

Commit

Permalink
Address comments
Browse files Browse the repository at this point in the history
  • Loading branch information
DOBEN committed Nov 30, 2023
1 parent d35bece commit 014d941
Show file tree
Hide file tree
Showing 4 changed files with 91 additions and 113 deletions.
22 changes: 6 additions & 16 deletions concordium-cis2/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1267,8 +1267,6 @@ pub struct OnReceivingCis2DataParams<T, A, D> {
}

/// Deserial trait for OnReceivingCis2DataParams<T, A, D>.
/// The specific type D is constructed from the general `AdditionalData` type
/// first before the `OnReceivingCis2DataParams` object is deserialized.
impl<T: Deserial, A: Deserial, D: Deserial> Deserial for OnReceivingCis2DataParams<T, A, D> {
fn deserial<R: Read>(source: &mut R) -> ParseResult<Self> {
let params: OnReceivingCis2Params<T, A> = source.get()?;
Expand All @@ -1283,21 +1281,13 @@ impl<T: Deserial, A: Deserial, D: Deserial> Deserial for OnReceivingCis2DataPara
}

/// Serial trait for OnReceivingCis2DataParams<T, A, D>.
/// The specific type D is converted into the general `AdditionalData` type
/// first before the `OnReceivingCis2Params<T, A>` object is serialized.
impl<T: Serial + Clone, A: Serial + Clone, D: Serial> Serial
for OnReceivingCis2DataParams<T, A, D>
{
impl<T: Serial, A: Serial, D: Serial> Serial for OnReceivingCis2DataParams<T, A, D> {
fn serial<W: Write>(&self, out: &mut W) -> Result<(), W::Err> {
let additional_data = AdditionalData::from(to_bytes(&self.data));
let object: OnReceivingCis2Params<T, A> = OnReceivingCis2Params {
token_id: self.token_id.clone(),
amount: self.amount.clone(),
from: self.from,
data: additional_data,
};

object.serial(out)?;
self.token_id.serial(out)?;
self.amount.serial(out)?;
self.from.serial(out)?;
(to_bytes(&self.data).len() as u16).serial(out)?;
out.write_all(&to_bytes(&self.data))?;
Ok(())
}
}
Expand Down
2 changes: 1 addition & 1 deletion examples/sponsored-tx-enabled-auction/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ concordium-cis2 = {path = "../../concordium-cis2", default-features = false}
[dev-dependencies]
concordium-smart-contract-testing = { path = "../../contract-testing" }
cis2-multi = { path = "../cis2-multi/" }
rand = "0.7.0"
rand = "0.7"

[lib]
crate-type=["cdylib", "rlib"]
178 changes: 83 additions & 95 deletions examples/sponsored-tx-enabled-auction/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,11 +36,6 @@
//! auction. The creator of that auction receives the highest bid when the
//! auction is finalized and the item is marked as sold to the highest bidder.
//! This can be done only once.
//!
//! Terminology: `Accounts` are derived from a public/private key pair.
//! `Contract` instances are created by deploying a smart contract
//! module and initializing it.
#![cfg_attr(not(feature = "std"), no_std)]

use concordium_cis2::{Cis2Client, *};
Expand Down Expand Up @@ -78,24 +73,24 @@ pub enum AuctionState {
#[derive(Debug, Serialize, SchemaType, Clone, PartialEq, Eq)]
pub struct ItemState {
/// State of the auction.
pub auction_state: AuctionState,
pub auction_state: AuctionState,
/// The highest bidder so far. The variant `None` represents
/// that no bidder has taken part in the auction yet.
pub highest_bidder: Option<AccountAddress>,
/// The item name to be sold.
pub name: String,
pub name: String,
/// The time when the auction ends.
pub end: Timestamp,
pub end: Timestamp,
/// The time when the auction starts.
pub start: Timestamp,
pub start: Timestamp,
/// In case `highest_bidder==None`, the minimum bid as set by the creator.
/// In case `highest_bidder==Some(AccountAddress)`, the highest bid that a
/// bidder has bid so far.
pub highest_bid: TokenAmountU64,
pub highest_bid: TokenAmountU64,
/// The `token_id` from the cis2 token contract used as payment token.
pub token_id: TokenIdU8,
pub token_id: TokenIdU8,
/// The account address that created the auction for this item.
pub creator: AccountAddress,
pub creator: AccountAddress,
}

/// The state of the smart contract.
Expand All @@ -106,43 +101,43 @@ pub struct ItemState {
#[concordium(state_parameter = "S")]
pub struct State<S = StateApi> {
/// A mapping including all items that have been added to this contract.
items: StateMap<u16, ItemState, S>,
items: StateMap<u16, ItemState, S>,
/// The cis2 token contract. Its tokens can be used to bid for items in this
/// contract.
cis2_contract: ContractAddress,
/// A counter that is sequentially increased whenever a new item is added to
/// the contract.
counter: u16,
counter: u16,
}

/// The return_value for the entry point `view` which returns the contract
/// state.
#[derive(Serialize, SchemaType, Debug, Eq, PartialEq)]
pub struct ReturnParamView {
/// A vector including all items that have been added to this contract.
pub item_states: Vec<(u16, ItemState)>,
pub item_states: Vec<(u16, ItemState)>,
/// The cis2 token contract. Its tokens can be used to bid for items in this
/// contract.
pub cis2_contract: ContractAddress,
/// A counter that is sequentially increased whenever a new item is added to
/// the contract.
pub counter: u16,
pub counter: u16,
}

/// The parameter for the entry point `addItem` that adds a new item to this
/// contract.
#[derive(Serialize, SchemaType)]
pub struct AddItemParameter {
/// The item name to be sold.
pub name: String,
pub name: String,
/// The time when the auction ends.
pub end: Timestamp,
pub end: Timestamp,
/// The time when the auction starts.
pub start: Timestamp,
pub start: Timestamp,
// The minimum bid that the first bidder has to overbid.
pub minimum_bid: TokenAmountU64,
// The `token_id` from the cis2 token contract that the item should be sold for.
pub token_id: TokenIdU8,
pub token_id: TokenIdU8,
}

/// The `additionData` that has to be passed to the `bid` entry point.
Expand Down Expand Up @@ -183,22 +178,34 @@ pub enum Error {
/// Raised when payment is attempted with a different `token_id` than
/// specified for an item.
WrongTokenID, //-11
/// Raised when the invocation of the cis2 token contract fails.
InvokeContractError, //-12
ParseResult, //-13
/// Raised when the parsing of the result from the cis2 token contract fails.
ParseResult, //-13
/// Raised when the response of the cis2 token contract is invalid.
InvalidResponse, //-14
/// Raised when the amount of cis2 tokens that was to be transferred is not available to the sender.
AmountTooLarge, //-15
/// Raised when the owner account of the cis 2 token contract that is being invoked does not
/// exist. This variant should in principle not happen, but is here for
/// completeness.
MissingAccount, //-16
/// Raised when the cis2 token contract that is to be invoked does not exist.
MissingContract, //-17
/// Raised when the cis2 token contract to be invoked exists, but the entry point that was named
/// does not.
MissingEntrypoint, //-18
// Raised when the sending of a message to the V0 contract failed.
MessageFailed, //-19
LogicReject, //-20
Trap, //-21
TransferFailed, //-22
// Raised when the cis2 token contract called rejected with the given reason.
LogicReject, //-20
// Raised when the cis2 token contract execution triggered a runtime error.
Trap, //-21
}

pub type ContractResult<A> = Result<A, Error>;

/// Mapping Cis2ClientError<Error> to Error
/// Mapping CallContractError<ExternCallResponse> to Error
impl From<CallContractError<ExternCallResponse>> for Error {
fn from(e: CallContractError<ExternCallResponse>) -> Self {
match e {
Expand Down Expand Up @@ -234,9 +241,9 @@ fn auction_init(ctx: &InitContext, state_builder: &mut StateBuilder) -> InitResu
let contract: ContractAddress = ctx.parameter_cursor().get()?;
// Creating `State`.
let state = State {
items: state_builder.new_map(),
items: state_builder.new_map(),
cis2_contract: contract,
counter: 0,
counter: 0,
};
Ok(state)
}
Expand Down Expand Up @@ -266,25 +273,28 @@ fn add_item(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractResult<()>
// Ensure start < end.
ensure!(item.start < item.end, Error::StartEndTimeError);

let slot_time = ctx.metadata().slot_time();
let block_time = ctx.metadata().block_time();
// Ensure the auction can run.
ensure!(slot_time <= item.end, Error::EndTimeError);
ensure!(block_time <= item.end, Error::EndTimeError);

// Assign an index to the item/auction.
let counter = host.state_mut().counter;
host.state_mut().counter = counter + 1;

// Insert the item into the state.
host.state_mut().items.insert(counter, ItemState {
auction_state: AuctionState::NotSoldYet,
highest_bidder: None,
name: item.name,
end: item.end,
start: item.start,
highest_bid: item.minimum_bid,
creator: sender_address,
token_id: item.token_id,
});
host.state_mut().items.insert(
counter,
ItemState {
auction_state: AuctionState::NotSoldYet,
highest_bidder: None,
name: item.name,
end: item.end,
start: item.start,
highest_bid: item.minimum_bid,
creator: sender_address,
token_id: item.token_id,
},
);

Ok(())
}
Expand Down Expand Up @@ -314,6 +324,8 @@ fn auction_bid(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractResult<(
bail!(Error::NotTokenContract);
}

let contract = host.state().cis2_contract;

// Getting input parameters.
let params: OnReceivingCis2DataParams<
ContractTokenId,
Expand All @@ -336,9 +348,9 @@ fn auction_bid(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractResult<(
// Ensure the auction has not been finalized yet.
ensure_eq!(item.auction_state, AuctionState::NotSoldYet, Error::AuctionAlreadyFinalized);

let slot_time = ctx.metadata().slot_time();
let block_time = ctx.metadata().block_time();
// Ensure the auction has not ended yet.
ensure!(slot_time <= item.end, Error::BidTooLate);
ensure!(block_time <= item.end, Error::BidTooLate);

// Ensure that the new bid exceeds the highest bid so far.
let old_highest_bid = item.highest_bid;
Expand All @@ -348,6 +360,12 @@ fn auction_bid(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractResult<(
item.highest_bid = params.amount;

if let Some(account_address) = item.highest_bidder.replace(bidder_address) {
let client = Cis2Client::new(contract);

let read_item = item.clone();

drop(item);

// Refunding old highest bidder.
// This transfer (given enough NRG of course) always succeeds because
// the `account_address` exists since it was recorded when it
Expand All @@ -356,23 +374,15 @@ fn auction_bid(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractResult<(
// Please consider using a pull-over-push pattern when expanding this
// smart contract to allow smart contract instances to
// participate in the auction as well. https://consensys.github.io/smart-contract-best-practices/attacks/denial-of-service/
let parameter = TransferParameter {
0: vec![Transfer {
token_id: item.token_id,
amount: old_highest_bid,
from: concordium_std::Address::Contract(ctx.self_address()),
to: concordium_cis2::Receiver::Account(account_address),
data: AdditionalData::empty(),
}],
};

drop(item);

host.invoke_contract(
&host.state().cis2_contract.clone(),
&parameter,
EntrypointName::new_unchecked("transfer"),
Amount::zero(),
client.transfer::<State, ContractTokenId, ContractTokenAmount, Error>(
host,
Transfer {
amount: old_highest_bid,
from: concordium_std::Address::Contract(ctx.self_address()),
to: concordium_cis2::Receiver::Account(account_address),
token_id: read_item.token_id,
data: AdditionalData::empty(),
},
)?;
}

Expand Down Expand Up @@ -401,15 +411,21 @@ fn auction_finalize(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractRes
// Ensure the auction has not been finalized yet.
ensure_eq!(item.auction_state, AuctionState::NotSoldYet, Error::AuctionAlreadyFinalized);

let slot_time = ctx.metadata().slot_time();
let block_time = ctx.metadata().block_time();
// Ensure the auction has ended already.
ensure!(slot_time > item.end, Error::AuctionStillActive);
ensure!(block_time > item.end, Error::AuctionStillActive);

if let Some(account_address) = item.highest_bidder {
// Marking the highest bidder (the last accepted bidder) as winner of the
// auction.
item.auction_state = AuctionState::Sold(account_address);

let client = Cis2Client::new(contract);

let read_item = item.clone();

drop(item);

// Sending the highest bid in tokens to the creator of the auction.
// This transfer (given enough NRG of course) always succeeds because
// the `creator` exists since it was recorded when it
Expand All @@ -418,44 +434,16 @@ fn auction_finalize(ctx: &ReceiveContext, host: &mut Host<State>) -> ContractRes
// Please consider using a pull-over-push pattern when expanding this
// smart contract to allow smart contract instances to
// participate in the auction as well. https://consensys.github.io/smart-contract-best-practices/attacks/denial-of-service/
// let parameter = TransferParameter {
// 0: vec![Transfer {
// token_id: item.token_id,
// amount: item.highest_bid,
// from: concordium_std::Address::Contract(ctx.self_address()),
// to: concordium_cis2::Receiver::Account(item.creator),
// data: AdditionalData::empty(),
// }],
// };

// drop(item);

let client = Cis2Client::new(contract);

let read_item = item.clone();

drop(item);

let success = client.transfer::<State, ContractTokenId, ContractTokenAmount, Error>(
client.transfer::<State, ContractTokenId, ContractTokenAmount, Error>(
host,
Transfer {
amount: read_item.highest_bid,
from: concordium_std::Address::Contract(ctx.self_address()),
to: concordium_cis2::Receiver::Account(read_item.creator),
amount: read_item.highest_bid,
from: concordium_std::Address::Contract(ctx.self_address()),
to: concordium_cis2::Receiver::Account(read_item.creator),
token_id: read_item.token_id,
data: AdditionalData::empty(),
data: AdditionalData::empty(),
},
)?;

// Ensure the transfer was successful ??? Shouldn't this return `true`
ensure!(!success, Error::TransferFailed);

// host.invoke_contract(
// &host.state().cis2_contract.clone(),
// &parameter,
// EntrypointName::new_unchecked("transfer"),
// Amount::zero(),
// )?;
}

Ok(())
Expand All @@ -473,9 +461,9 @@ fn view(_ctx: &ReceiveContext, host: &Host<State>) -> ContractResult<ReturnParam
let inner_state = state.items.iter().map(|x| (*x.0, x.1.clone())).collect();

Ok(ReturnParamView {
item_states: inner_state,
item_states: inner_state,
cis2_contract: host.state().cis2_contract,
counter: host.state().counter,
counter: host.state().counter,
})
}

Expand Down
2 changes: 1 addition & 1 deletion examples/sponsored-tx-enabled-auction/tests/tests.rs
Original file line number Diff line number Diff line change
Expand Up @@ -658,7 +658,7 @@ fn view_item_state(chain: &Chain, auction_contract_address: ContractAddress) ->
invoke.parse_return_value().expect("ViewItemState return value")
}

/// Get the `TOKEN_1` balances for Alice and the auction contract.
/// Get the `TokenIdU8(1)` balances for Alice and the auction contract.
fn get_balances(
chain: &Chain,
auction_contract_address: ContractAddress,
Expand Down

0 comments on commit 014d941

Please sign in to comment.