diff --git a/packages/hardhat/contracts/NunyaBusiness.sol b/packages/hardhat/contracts/NunyaBusiness.sol index 706d417..6f6da8f 100644 --- a/packages/hardhat/contracts/NunyaBusiness.sol +++ b/packages/hardhat/contracts/NunyaBusiness.sol @@ -180,11 +180,6 @@ contract NunyaBusiness { emit WithdrawalProcessed(_requestId, _code, _amount); } - // function fundGateway(uint256 _gas) internal returns (uint256) { - // gateway.transfer(_gas); - // return _gas; - // } - function fundGateway() internal { fundGateway(0); } diff --git a/packages/hardhat/contracts/payWithEth.sol b/packages/hardhat/contracts/payWithEth.sol new file mode 100644 index 0000000..e41414d --- /dev/null +++ b/packages/hardhat/contracts/payWithEth.sol @@ -0,0 +1,177 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.0; + +// TODO in production +import "hardhat/console.sol"; +// import "@openzeppelin/contracts/"; +// import "@openzeppelin/contracts/access/Ownable.sol"; + +interface SecretContract { + function newSecretUser(uint256 secret) external returns (uint256); + function linkPaymentRef(uint256 secret, string calldata ref) external returns (uint256); + function pay(string calldata ref, uint256 amount) external returns (uint256); + function payWithReceipt(string calldata ref, uint256 amount, uint256 userPubkey) external returns (uint256); + function withdraw(string calldata secret, address withdrawalAddress) external returns (uint256); + function retrievePubkey() external returns (uint256); +} + +/** + * @author + */ +contract NunyaBusiness { + + enum FunctionCallType { + OTHER, NEW_USER, NEW_REF, PAY, WITHDRAW, ERROR + } + + struct Receipt { + uint256 paymentRef; + uint256 amount; + bytes32 sig; + } + + address payable gateway; + SecretContract secretContract; + uint256 secretContractPubkey; + mapping (uint256 => FunctionCallType) expectedResult; + + event ReceiptEmitted(Receipt); + event RequestSuccess(uint256 requestId); + event SecretNetworkError(uint256 requestId, string message); + event HackingAttemptError(uint256 requestId); + + constructor(address payable _gateway) payable { + gateway = _gateway; + secretContract = SecretContract(_gateway); + fundGateway(msg.value); + + secretContract.retrievePubkey(); + // Lock secretContractPubkey to Owner. After it is set it cannot be reset. + secretContractPubkey = uint256(uint160(msg.sender)); + } + + modifier onlyGateway { + require (gateway!=address(0), "No gateway set"); + require (msg.sender==gateway, "Only gateway can call callbacks. Use the user function instead"); + _; + } + + function setSecretContractPubkeyCallback (uint256 _requestId, uint256 _key) public onlyGateway { + // require (secretContractPubkey==0, "Key already set"); + // Make sure it's our secret contract setting the key, not some interloper + // (will fail one time in 2^96 ;) + require (secretContractPubkey < 2**160, "Only the contract constructor can trigger this function"); + secretContractPubkey=_key; + } + + // Function wrapped in secret network payload encryption + function newSecretUser(uint256 _secret) public payable returns (uint256){ + fundGateway(msg.value); + uint256 requestId = secretContract.newSecretUser(_secret); + expectedResult[requestId]==FunctionCallType.NEW_USER; + return(requestId); + } + + function newSecretUserCallback(uint256 _requestId, bool _success) public onlyGateway { + require (expectedResult[_requestId]==FunctionCallType.NEW_USER); + if (!_success) + emit SecretNetworkError(_requestId, "Error paying - duplicate user?"); + emit RequestSuccess(_requestId); + } + + // Function wrapped in secret network payload encryption + function linkPaymentRef(uint256 _secret, string calldata _ref) public payable returns (uint256){ + fundGateway(msg.value); + uint256 requestId = secretContract.linkPaymentRef(_secret, _ref); + expectedResult[requestId]=FunctionCallType.NEW_REF; + return(requestId); + } + + function linkPaymentRefCallback(uint256 _requestId, bool _success) public onlyGateway{ + require (expectedResult[_requestId]==FunctionCallType.NEW_REF); + if (!_success) + emit SecretNetworkError(_requestId, "Error paying - no user found?"); + emit RequestSuccess(_requestId); + } + + // TODO: use ref encrypted with (user pubkey+salt) + function pay(string calldata _ref, uint256 _value) public payable returns (uint256) { + // >= because we need gas for + require (_value >= msg.value, "Naughty!"); + uint256 gasPaid = fundGateway(); + uint256 requestId = secretContract.pay(_ref, msg.value-gasPaid); + expectedResult[requestId]=FunctionCallType.PAY; + return(requestId); + } + + // TODO: use ref encrypted with (user pubkey+salt) + function pay(string calldata _ref, uint256 _value, uint256 _userPubkey) public payable returns (uint256) { + // >= because we need gas for + require (_value >= msg.value, "Naughty!"); + uint256 gasPaid = fundGateway(); + uint256 requestId = secretContract.payWithReceipt(_ref, msg.value-gasPaid, _userPubkey); + expectedResult[requestId]==FunctionCallType.PAY; + return(requestId); + } + + // useful? + // function payEncrypted(string EncryptedRef) payable { + // secretContract.pay() + // } + + function fundGateway(uint256 _gas) internal returns (uint256) { + gateway.transfer(_gas); + return _gas; + } + + function fundGateway() internal returns (uint256) { + // TODO: calculate gas better than this! + uint256 gas=1; + gateway.transfer(gas); + return gas; + } + + function payCallback(uint256 _requestId, bool _success) public payable onlyGateway { + require (expectedResult[_requestId]==FunctionCallType.PAY); + if (!_success) + emit SecretNetworkError(_requestId, "Error paying - wrong payment ref?"); + emit RequestSuccess(_requestId); + } + + function payCallback(uint256 _requestId, bool _success, Receipt calldata _receipt) public payable onlyGateway { + // TODO : use ecrecover to check receipt is signed by secret contract + require (expectedResult[_requestId]==FunctionCallType.PAY); + if (!_success) + emit SecretNetworkError(_requestId, "Error paying - wrong payment ref?"); + if (uint256(_receipt.sig)!=0) + emit ReceiptEmitted(_receipt); + emit RequestSuccess(_requestId); + } + + receive() external payable { + + } + + // Function wrapped in secret network payload encryption + function withdrawTo(string calldata _secret, uint256 _amount, address _withdrawalAddress) public payable returns (uint256) { + require((_amount > 0), "Account not found or empty."); + fundGateway(msg.value); + uint256 requestId = secretContract.withdraw(_secret, _withdrawalAddress); + // TODO: error check + expectedResult[requestId]=FunctionCallType.WITHDRAW; + return(requestId); + } + + function withdrawToCallback(uint256 _requestId, bool _success, uint256 _amount, address payable _withdrawalAddress) onlyGateway public { + require (expectedResult[_requestId]==FunctionCallType.WITHDRAW); + if (!_success) + emit SecretNetworkError(_requestId, "Error withdrawing - out of funds?"); + require(_amount > 0, "Account not found or empty."); + _withdrawalAddress.transfer(_amount); + emit RequestSuccess(_requestId); + } + + function emitSecretNetworkError(uint256 _requestId, string memory _message) public onlyGateway { + emit SecretNetworkError(_requestId, _message); + } +} diff --git a/packages/nextjs/app/page.tsx b/packages/nextjs/app/page.tsx index 90c054b..08a6292 100644 --- a/packages/nextjs/app/page.tsx +++ b/packages/nextjs/app/page.tsx @@ -24,6 +24,7 @@ const Home: NextPage = () => { const [returnAmount, setReturnAmount] = useState(""); const { writeContractAsync } = useScaffoldWriteContract("NunyaBusiness"); // const encoder: TextEncoder = new global.TextEncoder(); + // const decoder: TextDecoder = new global.TextDecoder(); useScaffoldWatchContractEvent({ contractName: "NunyaBusiness", diff --git a/packages/secret-contracts/nunya-contract/Makefile b/packages/secret-contracts/nunya-contract/Makefile index fa2d4f0..3723740 100644 --- a/packages/secret-contracts/nunya-contract/Makefile +++ b/packages/secret-contracts/nunya-contract/Makefile @@ -16,18 +16,22 @@ unit-test: # This is a local build with debug-prints activated. Debug prints only show up # in the local development chain (see the `start-server` command below) # and mainnet won't accept contracts built with the feature enabled. +# +# https://github.com/rust-bitcoin/rust-secp256k1/issues/283#issuecomment-1200858455 +# RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown .PHONY: build _build build: _build compress-wasm _build: - RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown - # RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown --features="debug-print" + cargo clean && AR=/opt/homebrew/opt/llvm/bin/llvm-ar CC=/opt/homebrew/opt/llvm/bin/clang RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown # This is a build suitable for uploading to mainnet. # Calls to `debug_print` get removed by the compiler. +# +# RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown .PHONY: build-mainnet _build-mainnet build-mainnet: _build-mainnet compress-wasm _build-mainnet: - RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown + cargo clean && AR=/opt/homebrew/opt/llvm/bin/llvm-ar CC=/opt/homebrew/opt/llvm/bin/clang RUSTFLAGS='-C link-arg=-s' cargo build --release --target wasm32-unknown-unknown # like build-mainnet, but slower and more deterministic .PHONY: build-mainnet-reproducible @@ -35,7 +39,7 @@ build-mainnet-reproducible: docker run --rm -v "$$(pwd)":/contract \ --mount type=volume,source="$$(basename "$$(pwd)")_cache",target=/contract/target \ --mount type=volume,source=registry_cache,target=/usr/local/cargo/registry \ - enigmampc/secret-contract-optimizer:1.0.10 + ghcr.io/scrtlabs/localsecret:v1.6.0-rc.3 .PHONY: compress-wasm compress-wasm: @@ -52,20 +56,16 @@ schema: .PHONY: start-server start-server: # CTRL+C to stop docker run -it --rm \ - -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ + -p 9091:9091 -p 26657:26657 -p 26656:26656 -p 1317:1317 -p 5000:5000 \ -v $$(pwd):/root/code \ - --name localsecret ghcr.io/scrtlabs/localsecret:v1.6.0 + --name secretdev ghcr.io/scrtlabs/localsecret:v1.6.0-rc.3 # This relies on running `start-server` in another console # You can run other commands on the secretcli inside the dev image -# by using `docker exec localsecret secretcli`. +# by using `docker exec secretdev secretcli`. .PHONY: store-contract-local store-contract-local: - docker exec localsecret secretcli tx compute store -y --from a --gas 1000000 /root/code/contract.wasm.gz - -.PHONY: integration-test -integration-test: - npx ts-node tests/integration.ts + docker exec secretdev secretcli tx compute store -y --from a --gas 1000000 /root/code/contract.wasm.gz .PHONY: clean clean: diff --git a/packages/secret-contracts/nunya-contract/cargo.toml b/packages/secret-contracts/nunya-contract/cargo.toml index bc64214..a410766 100644 --- a/packages/secret-contracts/nunya-contract/cargo.toml +++ b/packages/secret-contracts/nunya-contract/cargo.toml @@ -49,7 +49,6 @@ secret-toolkit-serialization = { version = "0.10.0", features = ["base64"] } tnls = { git = "https://github.com/SecretSaturn/TNLS", branch = "main", package = "secret_gateway", default-features = false } # cw-storage-plus = { version = "0.14.0", default-features = false } - [[bin]] name = "schema" required-features = ["schema"] diff --git a/packages/secret-contracts/nunya-contract/src/contract.rs b/packages/secret-contracts/nunya-contract/src/contract.rs index 11ddecb..29aee63 100644 --- a/packages/secret-contracts/nunya-contract/src/contract.rs +++ b/packages/secret-contracts/nunya-contract/src/contract.rs @@ -5,13 +5,13 @@ use crate::{ ResponseStoreMsg, ResponseRetrievePubkeyMsg, }, state::{ - NewSecretUser, LinkPaymentRef, Pay, PayEnryptedWithReceipt, WithdrawTo, + NewSecretUser, LinkPaymentRef, Pay, PayEncryptedWithReceipt, WithdrawTo, State, CONFIG, PUBKEY_MAP, NEW_SECRET_USER_MAP, LINK_PAYMENT_REF_MAP, PAY_MAP, PAY_ENCRYPTED_WITH_RECEIPT_MAP, WITHDRAW_TO_MAP, }, }; use cosmwasm_std::{ - entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, + entry_point, to_binary, Binary, Deps, DepsMut, Env, MessageInfo, Response, StdError, StdResult, Uint128, Uint256 }; use secret_toolkit::utils::{pad_handle_result, pad_query_result, HandleCallback}; use tnls::{ @@ -93,8 +93,7 @@ fn create_new_secret_user( // Parse as u256 let secret_user = input - ._secret - .parse::() + .secret_user .map_err(|err| StdError::generic_err(format!("Invalid _secret: {}", err)))?; let new_secret_user = NewSecretUser { @@ -152,14 +151,14 @@ fn create_link_payment_ref( let input: LinkPaymentRefStoreMsg = serde_json_wasm::from_str(&input_values) .map_err(|err| StdError::generic_err(err.to_string()))?; + // FIXME // Parse as u256 let secret_user = input - ._secret - .parse::() + .secret_user .map_err(|err| StdError::generic_err(format!("Invalid _secret: {}", err)))?; let payment_ref = input - ._ref + .payment_ref .parse::() .map_err(|err| StdError::generic_err(format!("Invalid _ref: {}", err)))?; @@ -220,19 +219,19 @@ fn create_pay( .map_err(|err| StdError::generic_err(err.to_string()))?; let payment_ref = input - ._ref + .payment_ref .parse::() .map_err(|err| StdError::generic_err(format!("Invalid _ref: {}", err)))?; // Parse as u256 - let value = input - ._value - .parse::() - .map_err(|err| StdError::generic_err(format!("Invalid _value: {}", err)))?; + let amount = input + .amount + .parse::() + .map_err(|err| StdError::generic_err(format!("Invalid _amount: {}", err)))?; let pay = Pay { payment_ref: payment_ref, - value: value, + amount: amount, }; // Extract KeyIter from Result, handle error if necessary @@ -287,25 +286,25 @@ fn create_pay_encrypted_with_receipt( .map_err(|err| StdError::generic_err(err.to_string()))?; let payment_ref = input - ._ref + .payment_ref .parse::() .map_err(|err| StdError::generic_err(format!("Invalid _ref: {}", err)))?; // Parse as u256 - let value = input - ._value - .parse::() - .map_err(|err| StdError::generic_err(format!("Invalid _value: {}", err)))?; + let amount = input + .amount + .parse::() + .map_err(|err| StdError::generic_err(format!("Invalid _amount: {}", err)))?; // Parse as u256 let user_pubkey = input - ._userPubkey - .parse::() + .user_pubkey + .parse::() .map_err(|err| StdError::generic_err(format!("Invalid _userPubkey: {}", err)))?; let pay_encrypted_with_receipt = PayEncryptedWithReceipt { payment_ref: payment_ref, - value: value, + amount: amount, user_pubkey: user_pubkey }; @@ -360,26 +359,21 @@ fn create_withdraw_to( let input: WithdrawToStoreMsg = serde_json_wasm::from_str(&input_values) .map_err(|err| StdError::generic_err(err.to_string()))?; - // Parse as String - // TODO - different from other _secret that is a u256 let secret_user = input - ._secret - .parse::() + .secret_user .map_err(|err| StdError::generic_err(format!("Invalid _secret: {}", err)))?; // Parse as u256 let amount = input - ._amount - .parse::() + .amount + .parse::() .map_err(|err| StdError::generic_err(format!("Invalid amount: {}", err)))?; - // Parse as u256 let withdrawal_address = input - ._withdrawalAddress - .parse::() + .withdrawal_address .map_err(|err| StdError::generic_err(format!("Invalid withdrawalAddress: {}", err)))?; - let withdrawal_to = WithdrawalTo { + let withdrawal_to = WithdrawTo { secret_user: secret_user, amount: amount, withdrawal_address: withdrawal_address diff --git a/packages/secret-contracts/nunya-contract/src/msg.rs b/packages/secret-contracts/nunya-contract/src/msg.rs index 6325e93..0eec5bc 100644 --- a/packages/secret-contracts/nunya-contract/src/msg.rs +++ b/packages/secret-contracts/nunya-contract/src/msg.rs @@ -1,4 +1,4 @@ -use cosmwasm_std::{Addr, Binary}; +use cosmwasm_std::{Addr, Binary, Uint128, Uint256}; use secret_toolkit::utils::HandleCallback; use tnls::msg::{PostExecutionMsg, PrivContractHandleMsg}; @@ -20,33 +20,33 @@ pub enum ExecuteMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct NewSecretUserStoreMsg { - pub secret_user: u256, + pub secret_user: Addr, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct LinkPaymentRefStoreMsg { - pub secret_user: u256, + pub secret_user: Addr, pub payment_ref: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct PayStoreMsg { pub payment_ref: String, - pub value: u256, + pub amount: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct PayEncryptedWithReceiptStoreMsg { pub payment_ref: String, - pub value: u256, - pub user_pubkey: u256, + pub amount: Uint128, + pub user_pubkey: Uint256, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct WithdrawToStoreMsg { - pub secret_user: u256, - pub amount: u256, - pub withdrawalAddress: String, // u160, // u160(u256(bytes32)) + pub secret_user: Addr, + pub amount: Uint128, + pub withdrawal_address: Addr, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -58,7 +58,7 @@ pub struct ResponseStoreMsg { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] #[serde(rename_all = "snake_case")] pub enum QueryMsg { - RetrievePubkey { key: u256 }, + RetrievePubkey { key: u32 }, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] diff --git a/packages/secret-contracts/nunya-contract/src/state.rs b/packages/secret-contracts/nunya-contract/src/state.rs index 0e334ec..46ec464 100644 --- a/packages/secret-contracts/nunya-contract/src/state.rs +++ b/packages/secret-contracts/nunya-contract/src/state.rs @@ -1,15 +1,15 @@ -use cosmwasm_std::{Addr, Binary}; +use cosmwasm_std::{Addr, Binary, Uint128, Uint256}; use secret_toolkit::storage::{Item, Keymap}; use schemars::JsonSchema; use serde::{Deserialize, Serialize}; pub static CONFIG: Item = Item::new(b"config"); -pub static PUBKEY_MAP: Keymap> = Keymap::new(b"PUBKEY_MAP"); +pub static PUBKEY_MAP: Keymap> = Keymap::new(b"PUBKEY_MAP"); pub static NEW_SECRET_USER_MAP: Keymap> = Keymap::new(b"NEW_SECRET_USER_MAP"); pub static LINK_PAYMENT_REF_MAP: Keymap> = Keymap::new(b"LINK_PAYMENT_REF_MAP"); -pub static PAY_MAP: Keymap> = Keymap::new(b"PAY_MAP"); -pub static PAY_ENCRYPTED_WITH_RECEIPT_MAP: Keymap> = Keymap::new(b"PAY_ENCRYPTED_WITH_RECEIPT_MAP"); +pub static PAY_MAP: Keymap> = Keymap::new(b"PAY_MAP"); +pub static PAY_ENCRYPTED_WITH_RECEIPT_MAP: Keymap> = Keymap::new(b"PAY_ENCRYPTED_WITH_RECEIPT_MAP"); pub static WITHDRAW_TO_MAP: Keymap> = Keymap::new(b"WITHDRAW_TO_MAP"); #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] @@ -21,31 +21,31 @@ pub struct State { #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct NewSecretUser { - pub secret_user: u256, + pub secret_user: Addr, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct LinkPaymentRef { - pub secret_user: u256, + pub secret_user: Addr, pub payment_ref: String, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct Pay { pub payment_ref: String, - pub value: u256, + pub amount: Uint128, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] -pub struct PayEnryptedWithReceipt { +pub struct PayEncryptedWithReceipt { pub payment_ref: String, - pub value: u256, - pub user_pubkey: u256, + pub amount: Uint128, + pub user_pubkey: Uint256, } #[derive(Serialize, Deserialize, Clone, Debug, PartialEq, JsonSchema)] pub struct WithdrawTo { - pub secret_user: u256, - pub amount: u256, - pub withdrawalAddress: String, // u160, // u160(u256(bytes32)) + pub secret_user: Addr, + pub amount: Uint128, + pub withdrawal_address: Addr, }