0
votes

I have a contract which holds tokens on behalf of addresses and transfers them when a signed hash of a transfer is provided.

The contract looks like this

pragma solidity ^0.5.0;

import "openzeppelin-solidity/contracts/ownership/Ownable.sol";
import "openzeppelin-solidity/contracts/token/ERC20/IERC20.sol";
import "openzeppelin-solidity/contracts/math/SafeMath.sol";

contract Settlement is Ownable {
    using SafeMath for uint256;

    struct Withdrawal {
        uint256 amount;
        address token;
        uint256 timestamp;
    }

    // token => (holder => balance)
    mapping(address => mapping(address => uint256)) public tokenBalances;
    mapping(address => Withdrawal) withdrawals;

    function transferInto(address recipient, uint256 amount, address token) public {
        //get the tokens
        IERC20(token).transferFrom(msg.sender, address(this), amount);
        //increase the token balance in the payment contract
        tokenBalances[token][recipient] = tokenBalances[token][recipient].add(amount);
    }

    string constant private prefix = "\u0019Ethereum Signed Message:\n32";
    function transfer(address to, uint256 amount, address token, uint8 v, bytes32 r, bytes32 s)
    public {
        bytes32 paramHash = keccak256(abi.encodePacked(to, amount, token));
        address signer = ecrecover(keccak256(abi.encodePacked(prefix, paramHash)), v, r, s);
        //SafeMath ensures that the signer has enough tokens in their payment account
        tokenBalances[token][signer] = tokenBalances[token][signer].sub(amount);

        IERC20(token).transfer(to, amount);
    }

}

and I have written a function to create the signature which is passed to the transfer function of the contract:

const ethers = require('ethers')
const BigNumber = require('bignumber.js')
const utils = require('web3-utils')

// the purpose of this function is to be able to create BN from exponent numbers like '2e22' they must be formatted as string in this case
const toBN = (num) => utils.toBN(new BigNumber(num).toString(10))

async function signTransfer(recipient, amount, tokenAddress, privateKey){
  const wallet = new ethers.Wallet(privateKey)
  const txMsg = utils.soliditySha3(recipient, toBN(amount), tokenAddress)
  const messageHashBytes = ethers.utils.arrayify(txMsg)
  const flatSig = await wallet.signMessage(messageHashBytes)
  const sig = ethers.utils.splitSignature(flatSig)

  return {
    ...sig,
    hash: messageHashBytes
  }
}

module.exports = signTransfer

this works but I had to use both ethers and web3-utils packages to implement this.

how can I replace the soliditySha3 function with an ethers version?

I had a look at the implementation of soliditySha3 and it looks mighty complex.

The thing is that web3js does not seem to have a function to create the messageHashBytes in my function. So I'm stuck with both. It's not super bad, but it would be nice to reduce the number of libraries.

1

1 Answers

0
votes

If you're okay just using web3.js for everything, something like this should work:

function signTransfer(recipient, amount, tokenAddress, privateKey) {
  return web3.eth.accounts.sign(
    web3.utils.soliditySha3(recipient, toBN(amount), tokenAddress),
    privateKey);
}