This smart contract allows trustless gifting of ERC20/ERC721/ERC1115/native tokens (hereafter referred to as tokens
) to unknown recipients.
Basic Glossary:
creator
- the person giving away tokens by sharing asecret sharing code
with the receiverreceiver
- the person who has the secret sharing code and is allowed to claim a giftsharing code
- a set of random/custom strings used as a base "salt" to generate a private key of theverifier
verifier
- a signer used in the verification process to verify the validity of a gift and the receiver
- The
creator
creates a gift with giftable content and the address of a verifier (the verifier is generated in the UI based on the provided customsharing code
or randomly generatedsharing code
) - The
creator
shares thesharing code
with the receiver - The
receiver
opens a Gifting UI where thesharing code
is decoded to theverifier
signer. The claiming signature is generated in the background when thereceiver
connects their wallet. - The signature contains the
receiver
address. - This signature can be used by the
receiver
or any "operator" (if thereceiver
has no RON for gas fees) to claim the gift usingclaimGift(giftID, receiver, signature)
. The gift is transferred to the receiver.
- The
creator
can set additional restrictions during gift creation, such as:- Atia's Blessing status
- Atia's Blessing strike higher than
x
- Holding a specific amount of ERC721, ERC20 or native tokens
- Whoever has the
sharing code
must meet the defined criteria to successfully claim the gift.
Gifts.sol
- Ownable contract logic. The owner can change the logic address of theRestriction Controller
and introduce new restrictions.RestrictionControl.sol
- Disposable contract that introduces gift restrictions. It can be redeployed and the implementation address can be changed inGifts.sol
.
All active/claimed/cancelled gifts are stored in this mapping. Should we be concerned about the potential size? If so, we can remove claimed/cancelled gifts from the mapping to reduce its size. The downside would be that thereceiver
would not know the reason why they cannot claim their gift (whether the creator cancelled it or someone else claimed it quicker). This could lead to higher gas usage for claiming/cancellations.
Over time, it will collect addresses of all used verifiers in the past (for active/claimed/cancelled gifts). The purpose is to block the re-use of already used verifiers. If this is a valid concern, then I could delete verifiers when a gift is being cancelled or claimed. This could lead to higher gas usage for claiming/cancellations.
#3 Giving away too much info could benefit attackers? ✅ No risk exposing wheter invalid gift was claimed or cancelled
ShouldgetGift
return cancelled/claimed gifts?ShouldcancelGift
andclaimGift
revert with specific reasons why a gift cannot be cancelled/claimed? (already claimed/cancelled)?
Maybe this is a completely pointless check, and removing it could save some gas.
The main contract is not upgradable on purpose - it holds gifted tokens, and a malicious upgrade could introduce ways to take them all.
There are 2 options for handling the upgradeability of the restriction controller (currently I went with option#1
):
Gifts.sol
is Ownable, and onlyOwner can change the implementation address. If a new version is out, then the owner would just change the address.Gifts.sol
would not need to be Ownable. We would deploy theRestriction Controller
as an upgradable transparent proxy and use the proxy address in theGifts.sol
constructor. If a new version is out, then we would upgrade the proxy, andGifts.sol
would remain untouched.
The gift creator can passbytes
during the gift creation. The Restriction Controller'scheckRestriction
is called with such bytes to verify the restriction exists, and then it is stored inallGifts[giftID].restrictions.args
.My concern is if a malicious gift creator can use specifically craftedbytes
to somehow exploit the restriction controller, allowing them to do harm to the contract or allowing them to use the created gift with maliciousbytes
to claim/retrieve tokens from the contract that they are not supposed to.
-
Should we add an onlyOwner function to cancel gifts on behalf of someone else?
-
Or make contract pausable and add emergencyExit to cancel all existing gifts, return assets to owners and pause contract?
-
Should the contract be pausable?
✅ Added
emergencyExit
, just in case
yarn hardhat deploy --network <ronin|saigon>