Skip to content

Commit

Permalink
Solidity Coverage - Signature example (#533)
Browse files Browse the repository at this point in the history
- Adding test coverage for signature example and its tests,
- Also fixed a typo on TypeOps tests

---------
Signed-off-by: Alfredo Gutierrez <[email protected]>
Signed-off-by: Nana Essilfie-Conduah <[email protected]>
Co-authored-by: Nana Essilfie-Conduah <[email protected]>
  • Loading branch information
AlfredoG87 authored Nov 10, 2023
1 parent bb9ec84 commit 3de889b
Show file tree
Hide file tree
Showing 4 changed files with 163 additions and 3 deletions.
64 changes: 64 additions & 0 deletions contracts/solidity/signature-example/ReceiverPays.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
// SPDX-License-Identifier: Apache-2.0
pragma solidity >=0.7.0 <0.9.0;

contract ReceiverPays {
address owner = msg.sender;

mapping(uint256 => bool) usedNonces;

constructor() payable {}

function claimPayment(uint256 amount, uint256 nonce, bytes memory signature) external {
require(!usedNonces[nonce], "invalid nonce");
usedNonces[nonce] = true;

// this recreates the message that was signed on the client
bytes32 message = prefixed(keccak256(abi.encodePacked(msg.sender, amount, nonce, this)));

require(recoverSigner(message, signature) == owner, "invalid signature");

payable(msg.sender).transfer(amount);
}

/// destroy the contract and reclaim the leftover funds.
function shutdown() external {
require(msg.sender == owner, "only owner can shutdown");
// This will report a warning due to deprecated selfdestruct
selfdestruct(payable(msg.sender));
}

/// signature methods.
function splitSignature(bytes memory sig)
internal
pure
returns (uint8 v, bytes32 r, bytes32 s)
{
require(sig.length == 65);

assembly {
// first 32 bytes, after the length prefix.
r := mload(add(sig, 32))
// second 32 bytes.
s := mload(add(sig, 64))
// final byte (first byte of the next 32 bytes).
v := byte(0, mload(add(sig, 96)))
}

return (v, r, s);
}

function recoverSigner(bytes32 message, bytes memory sig)
internal
pure
returns (address)
{
(uint8 v, bytes32 r, bytes32 s) = splitSignature(sig);

return ecrecover(message, v, r, s);
}

/// builds a prefixed hash to mimic the behavior of eth_sign.
function prefixed(bytes32 hash) internal pure returns (bytes32) {
return keccak256(abi.encodePacked("\x19Ethereum Signed Message:\n32", hash));
}
}
1 change: 1 addition & 0 deletions test/constants.js
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,7 @@ const Path = {
HIP583_ERC721Mock: 'contracts/hip-583/ERC721Mock.sol:ERC721Mock',
HRC: 'contracts/hrc/HRC.sol:HRC',
TYPE_OPS: 'contracts/solidity/typeops/TypeOps.sol:TypeOps',
RECEIVER_PAYS: 'contracts/solidity/signature-example/ReceiverPays.sol:ReceiverPays',
}

const Contract = {
Expand Down
97 changes: 97 additions & 0 deletions test/solidity/signature-example/ReceiverPays.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*-
*
* Hedera Smart Contracts
*
* Copyright (C) 2023 Hedera Hashgraph, LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
const { expect } = require('chai')
const { ethers } = require('hardhat')
const Constants = require('../../constants')
const abi = require('ethereumjs-abi');

describe('@solidityequiv4 Signature Example ReceiverPays Test Suite', function () {
let receiverPaysContract, provider, signers, currentNonce, sender, receiver;

before(async function () {
signers = await ethers.getSigners();
sender = signers[0];
receiver = signers[1];
ethers.provider = sender.provider;
provider = ethers.provider;
const factory = await ethers.getContractFactory(Constants.Path.RECEIVER_PAYS);
const initialFund = ethers.utils.parseEther('4');
receiverPaysContract = await factory.deploy({ gasLimit: 15000000, value: initialFund });
currentNonce = 0;
});

// claim payment
it('receiver should be able to claim payment and pay for transaction fees', async function () {
const recipientAddress = receiver.address;
const contractBalanceBefore = await signers[0].provider.getBalance(receiverPaysContract.address);
// There is a discrepancy between the amount of decimals for 1 ETH and 1 HBAR. see the tinybar to wei coefficient of 10_000_000_000
// it should be ethers.utils.parseEther('1');
const amountToTransfer = 100000000;

// Generate signature for payment
const signedPayment = await signPayment(recipientAddress, amountToTransfer, currentNonce, receiverPaysContract.address);

// Claim payment
const contract = receiverPaysContract.connect(receiver);
await contract.claimPayment(amountToTransfer, currentNonce, signedPayment);

// Verify payment is received
const contractBalanceAfter = await signers[0].provider.getBalance(receiverPaysContract.address);

expect(contractBalanceAfter).to.equal(contractBalanceBefore.sub(ethers.utils.parseEther('1')));

currentNonce++;
});

// try to shutdown contract as receiver
it('receiver should not be able to shutdown contract', async function () {
const contract = receiverPaysContract.connect(receiver);
let errorOccurred = false;
try {
const tx = await contract.shutdown();
await tx.wait();
} catch (error) {
expect(error.reason).to.be.equal("transaction failed");
errorOccurred = true;
}
expect(errorOccurred).to.be.true;
// verify the contract still has balance
const contractBalance = await signers[0].provider.getBalance(receiverPaysContract.address);
expect(contractBalance).to.be.gt(0);
});

// should be able to shutdown as sender
it('sender should be able to shutdown contract', async function () {
const contract = receiverPaysContract.connect(sender);
await contract.shutdown();
// verify contract is shutdown, contract should have no balance left
const contractBalance = await signers[0].provider.getBalance(receiverPaysContract.address);
expect(contractBalance).to.be.equal(0);
});


async function signPayment(recipient, amount, nonce, contractAddress) {
const hash = ethers.utils.solidityKeccak256(["address", "uint256", "uint256", "address"],[recipient, amount, nonce, contractAddress])
// Sign the hash
const signature = await sender.signMessage(ethers.utils.arrayify(hash));
return signature;
}

});
4 changes: 1 addition & 3 deletions test/solidity/typeops/TypeOps.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const { ethers } = require('hardhat')
const Constants = require('../../constants')

describe('@solidityevmequiv1 TypeOps Test Suite', function () {
let cryptoMathContract, provider, signers;
let typeOpsContract, provider, signers;

before(async function () {
signers = await ethers.getSigners();
Expand Down Expand Up @@ -85,6 +85,4 @@ describe('@solidityevmequiv1 TypeOps Test Suite', function () {
const res = await typeOpsContract.typeUintMax();
expect(res).to.equal(expectedUintMax);
});


});

0 comments on commit 3de889b

Please sign in to comment.