Game of Straws

An experiment for Light Contracts and Oracles on Ardor

Recently we ran a giveaway promotion for Bitswift.shop. We asked the question, how can we use the blockchain to determine the winner of the giveaway, and how can we make the results transparent so that all participants could verify the following:

1. They were entered for a chance to win

2. The odds of receiving the giveaway were equal

3. Everyone could verify the winner

To fulfill the promotion requirements the following steps were performed:

  1. We created an account on the blockchain (Ardor testnet) for all users who were eligible for the giveaway. Example: “This User = That Account”
  2. The Oracle account “ ARDOR-NFAY-XYBA-SZ4G-H8SHA“ set an “Account Property” on each eligible account to “BITSWIFTWINNER”, tagging all eligible accounts with this same property. The contract will only look at these accounts that have been tagged.
Setting properties on each eligible account

3. Send a message from the Oracle account back to itself to activate contract

Message:

{ “chain”: 2, “fullHash”: “6acad5c52559f956c8287bf0c9e683eaf0ac49044020d3c0ffda32c80041724d”, “params”: { “property”: “BITSWIFTWINNER” }}

You can read this message as; activate contract 6aca.. from chain 2 with parameter “property” set to “BITSWIFTWINNER” and execute.

The result of the message was the Oracle executing the contract which immediately returned a transaction changing the account property on one of the tagged “BITSWIFTWINNER” accounts to value “winner:141614” where 141614 is the block height in which the winner was stamped.

Results of running contract

In this case the account ARDOR-H6CY-FDPT-EJ4Z-BR4RG was chosen and tagged “winner:141614”, this account was assigned to a specific user (@lleits) and as such the user has received a nice new WD SSD on behalf of Bitswift.shop.

Congrats @lleits, the blockchain has chosen you!

Contract code:

Contact Class:
package nxt.addons.contracts;
import nxt.addons.TransactionContext;
import nxt.util.Logger;
import java.util.List;
import java.util.Random;
import java.util.stream.Collectors;
import static nxt.blockchain.ChildChain.IGNIS;
public class PropertyBasedLottery extends AbstractContract {
public PropertyBasedLottery(String name, String version) {
super(name, version);
}
@Override
public JO processTransaction(TransactionContext context) {
return selectWinner(context);
}
private JO selectWinner(TransactionContext context) {
JO notSameRecipient = context.validateSameRecipient();
if (notSameRecipient != null) {
return notSameRecipient;
}
JO notSameSender = context.validateSameSender();
if (notSameSender != null) {
return notSameSender;
}
JO response = new JO();
String propertyKey = context.getRuntimeParams().getString("property");
if (propertyKey == null) {
response.put("errorDescription", "Property based on which to perform the lottery not specified in contract params");
return response;
}
String accountRS = context.getConfig().getString("accountRS");
JO params = new JO();
params.put("property", propertyKey);
params.put("setter", accountRS);
JO getAccountProperties = context.sendRequest("getAccountProperties", params);
JA properties = getAccountProperties.getArray("properties");
if (properties.size() == 0) {
response.put("errorDescription", String.format("No accounts with property %s set by %s found", propertyKey, context.getConfig().getString("accountRS")));
return response;
}
List<JO> nonWinners = properties.objects().stream().filter(p ->  {
String value = p.getString("value");
return value == null || !value.startsWith("winner");
}).collect(Collectors.toList());
if (nonWinners.size() == 0) {
response.put("errorDescription", "No non-winning accounts found");
return response;
}
Random r = context.getRandom();
int winner = r.nextInt(nonWinners.size());
String winnerAccountRS = nonWinners.get(winner).getString("recipientRS");
Logger.logInfoMessage(String.format("winner account is %s", winnerAccountRS));
JO input = new JO();
input.put("recipient", winnerAccountRS);
input.put("property", propertyKey);
input.put("value", "winner:" + context.getBlockJson().getInt("height"));
JO transactionResponse = context.createTransaction("setAccountProperty", IGNIS, null, input);
JO contractResponse = new JO();
JA transactions = new JA();
transactions.add(transactionResponse);
contractResponse.put("transactions", transactions);
return contractResponse;
}
}

Note: Ignis and Testnet were chosen based on the fact that we are experimenting and we have a testnet account loaded with Ignis, with slight modification the same contract would execute on the Bitswift blockchain.

More Information:

Source code used to compile contract class: https://mega.nz/#!dkpFzJgI!OJVSOcBxRs7AtqMIKSgY2JMXr26eUzXWAVO_6Y1UQGw

Lightweight Contracts on Ardor:

https://medium.com/coinmonks/lightweight-contracts-b738b6e29377

Lightweight contract FAQs:

https://medium.com/@lyaffe/lightweight-contracts-faq-30273120da9a

More Advanced Contracts on Ardor:

https://medium.com/@lyaffe/more-advanced-contracts-46865515743d

Oracle Contracts on Ardor:

Special thanks to Jelurida for help in developing this example.

-Bitswift