Conditional Payments in Move?

I read the short guide on Move and understand that it’s purposely much simpler than eg. Solidity.

However, implementing certain rules seems possible, so my question is:

Given the below, very easy, conditional payment (Sender deploys contract with hashed secret, recipient and some Eth value, recipient/anybody can call conditionalSend with the unhashed secret and Eth is send to recipient address), can this be done with Move?

pragma solidity >=0.4.22 <0.6.0;

contract ConditionalPayment {

    address public recipient;
    uint256 public value;
    bytes32 public hashLock;
    
    event Transferred(address _recipient, uint256 _value);

    constructor(address _recipient, bytes32 _hashLock) payable public {
        recipient = _recipient;
        value = msg.value;
        hashLock = _hashLock;
    }

    function conditionalSend(string challenge) public {
        if (sha256(challenge) == hashLock) {
            recipient.transfer(value);
            emit Transferred(recipient, value);
        }
    }

}
4 Likes

Here is how I would write this in Move [0]:

module HashPuzzle {
  import 0x0.Hash;
  import 0x0.LibraCoin;

  resource T { target: bytearray, reward: R#LibraCoin.T }

  public create(target: bytearray, reward: R#LibraCoin.T) {
    let t: R#Self.T;

    t = T { target: move(target), reward: move(reward) };
    move_to_sender<T>(move(t));
  }

  public solve(solution: bytearray, puzzle_address: address): R#LibraCoin.T {
    let t: R#Self.T;
    let target: bytearray;
    let hash: bytearray;
    let reward: R#LibraCoin.T;

    t = move_from<T>(move(puzzle_address));
    T { target: target, reward: reward } = move(t);

    hash = Hash.sha3_256(move(target));
    assert(move(hash) == move(solution), 77);

    // return the reward to the caller, who can deposit it wherever they wish
    // we could have also used LibraAccount.deposit, but that is less flexible
    return move(reward);
  }

}

[0] I wouldn’t recommend writing this sort of code at all because it’s vulnerable to frontrunning. If someone sees the transaction that reveals solution and gets their own transaction in before, they can steal the reward.

1 Like

Thanks!
So conditions are no problem. There is a logical difference in both versions though, which makes your version frontrunnable: in the Solidity version, the recipient is set on creation, so frontrunning conditionalSend doesn’t help.
Could you clone the logic 1:1 by fixating the recipient address on contract creation?
Then frontrunning shouldn’t be possible?

1 Like

Good point, I neglected to notice the fixed recipient feature in your original code!

We could indeed replicate that in the Move version by adding recipient as a field in the resource + checking it against the sender address in solve.

That would be possible, but not really copy the original behaviour.
The logic of the Solidity contract is:

  1. Alice deploys the contract with value X and fixed recipient and “hashlock”
  2. Bob has to execute a tasks and gets the original secret from Alice
  3. Bob or any other person or contract can call conditionalSend with the secret, the contract checks the hashlock and transfers the value stored immutably in the contract until conditionalSend is called to Bob

With your example I am missing that the value is stored in the contract until the correct challenge is submitted or forever, if the challenge is not submitted. If the challenge is submitted, the recipient is fixed and cannot be changed after contract creation.
With this simple logic, Bob can be sure to get the money if and only if he gets the secret by Alice and nobody can change this.

Would be great for me to see the exact logic in Move to learn the language better.

As there are so many tools in the Ethereum space, having a clone of Remix IDE would be a great achievement. Truffle and Ganache next :wink:
And an API (JSON/RPC or REST) would be great as eg. I am using this from Java/JVM and many others from JavaScript or Go (btw: why oh why haven’t you chosen Kotlin, that would have been so cool :slight_smile: )

1 Like

Thanks for explaining; I think I understand the requirements now. Here is how to do this in Move:

module HashPuzzle {
  import 0x0.Hash;
  import 0x0.LibraAccount;
  import 0x0.LibraCoin;

  resource T { target: bytearray, recipient: address, reward: R#LibraCoin.T }

  public create(target: bytearray, recipient: address, reward: R#LibraCoin.T) {
    let t: R#Self.T;

    t = T { target: move(target), recipient: move(recipient), reward: move(reward) };
    move_to_sender<T>(move(t));

    return;
  }

  // can be called by anyone that knows the correct solution
  public solve(solution: bytearray, puzzle_address: address) {
    let t: R#Self.T;
    let target: bytearray;
    let recipient: address;
    let hash: bytearray;
    let reward: R#LibraCoin.T;

    t = move_from<T>(move(puzzle_address));
    T { target: target, recipient: recipient, reward: reward } = move(t);

    hash = Hash.sha3_256(move(solution));
    assert(move(target) == move(hash), 77);

    // puzzle solved. send the reward to the intended recipient
    LibraAccount.deposit(move(recipient), move(reward));

    return;
  }

}
3 Likes

Yes, this seems to implement to same logic as the Solidity version.
Just one more thing, which might be related to this module/resource/procedure concept: in the Solidity version, the ETH value is stored “at the contract address” on contract creation, so neither Alice nor Bob have access to the value, guaranteed by the EVM/Blockchain, until the ETH is released by the hashlock.
In the Move version, move_to_sender<T> and move_from<T> is used, which to me looks like the sender, Alice, gets ownage of the Resource until the hashlock releases it to Bob?

Could you point me to a page explaining how I can test this against a local or the testnet version (eg. how do I deploy this contract to some chain and how can I call it)?

Thanks!

Could somebody answer the question above? I would like to understand how, in an agreement between Alice and Bob (about ownage of a certain Resource), it can be made sure that the sender (Alice) has no access to the Resource if the conditions are not met?
In Ethereum, the asset is transferred to the smart contract address, which makes sure nobody (especially not Alice and Bob) can access it until some conditions are met.

1 Like

good question, expecting team answer also @sam @aching

In the Move version, move_to_sender and move_from is used, which to me looks like the sender, Alice, gets ownage of the Resource until the hashlock releases it to Bob?

Good question. The account that a resource is stored under does not have any special privileges [1]. The rules encoded in the procedure of a module determine who can move a resource published under and account and where they can move it to.

In the specific case of the HashPuzzle example, the resource is stored under Alice’s account, but the logic in solve ensures that (1) the resource can only be removed from Alice’s account by someone who knows how to solve the puzzle and (2) the reward part of the resource can only be moved to Bob’s account.

[1] There is one exception to this rule, which is that only the account owner can choose to move a resource under her account via move_to_sender. The rationale for this is explained in more detail here.

1 Like

Thanks. Just to make sure: even though the Resource is stored in Alice account, even she has no possibility to “move” it somewhere else? Like external of the contract, eg. with some other module, which accesses the “same” Resource?

Yes, that is correct.

1 Like