Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Solidity Coverage - Signature example #533

Merged
merged 6 commits into from
Nov 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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',
Nana-EC marked this conversation as resolved.
Show resolved Hide resolved
}

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);
});


});
Loading