diff --git a/.gitignore b/.gitignore index 0fda7a0..509a06b 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .idea/ **/README.md + +**/build diff --git a/transfer_expiry/.gitignore b/transfer_expiry/.gitignore new file mode 100644 index 0000000..f721f7f --- /dev/null +++ b/transfer_expiry/.gitignore @@ -0,0 +1,5 @@ +.env +*.avm +*.prover +*.verifier +outputs/ diff --git a/transfer_expiry/program.json b/transfer_expiry/program.json new file mode 100644 index 0000000..0cfb948 --- /dev/null +++ b/transfer_expiry/program.json @@ -0,0 +1,16 @@ +{ + "program": "transfer_public_expiry.aleo", + "version": "0.1.0", + "description": "", + "license": "MIT", + "leo": "3.2.0", + "dependencies": [ + { + "name": "credits.aleo", + "location": "network", + "path": null, + "edition": null + } + ], + "dev_dependencies": null +} diff --git a/transfer_expiry/src/main.leo b/transfer_expiry/src/main.leo new file mode 100644 index 0000000..6fa232a --- /dev/null +++ b/transfer_expiry/src/main.leo @@ -0,0 +1,56 @@ +// The 'transfer_public_expiry' program. +import credits.aleo; + +program transfer_public_expiry.aleo { + + // Struct to hold address and nonce for hashing + struct NonceKey { + sender: address, + nonce: u64, + } + + // Mapping to store used nonces for each sender address + // Key is the NonceKey struct containing address and nonce + mapping used_nonces: NonceKey => bool; + + // Default constructor which doesn't allow for program upgrades. + @noupgrade + async constructor() {} + + // Wrapper around credits.aleo::transfer_public with an expiry and nonce validation. + async transition transfer_public_with_expiry( + public to: address, + public amount: u64, + public expiry_block_height: u32, + public nonce: u64 + ) -> Future { + // Get the sender's address before entering async block + let sender: address = self.caller; + + // Call the original transfer_public function from credits.aleo + let f1 = credits.aleo/transfer_public(to, amount); + + let f2: Future = async { + // Create a NonceKey struct with the sender address and nonce + let nonce_key: NonceKey = NonceKey { + sender: sender, + nonce: nonce, + }; + + // Check if the nonce has already been used for this sender + let is_used: bool = used_nonces.get(nonce_key); + assert(!is_used); + + // Store the nonce as used for this sender + used_nonces.set(nonce_key, true); + + // Check if current block height is below the expiry block height + let current_block_height: u32 = block.height; + assert(current_block_height < expiry_block_height); + f1.await(); + }; + + return f2; + } +} + diff --git a/transfer_expiry/tests/test_transfer_public_expiry.leo b/transfer_expiry/tests/test_transfer_public_expiry.leo new file mode 100644 index 0000000..76078db --- /dev/null +++ b/transfer_expiry/tests/test_transfer_public_expiry.leo @@ -0,0 +1,27 @@ +// The 'test_transfer_public_expiry' test program. +import transfer_public_expiry.aleo; + +program test_transfer_public_expiry.aleo { + // Default constructor which doesn't allow for program upgrades. + @noupgrade + async constructor() {} + + @test + async transition test_expiry() -> Future { + // Test the transfer_public_with_expiry function + // This would normally require proper setup with credits, but demonstrates the interface + let to: address = aleo1kanvv2cs88dpguwz5yrv890zz89rmycyk7qe6q4qm7vnz30flyzs56zae8; + let amount: u64 = 1000u64; + let expiry_block_height: u32 = 1000000u32; + let nonce: u64 = 1u64; + + // Call the wrapper function + let f1 = transfer_public_expiry.aleo/transfer_public_with_expiry(to, amount, expiry_block_height, nonce); + + let f2 = async { + f1.await(); + }; + + return f2; + } +}