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

Delegate and SwapERC20 Finalizations #1322

Merged
merged 4 commits into from
Aug 28, 2024
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
93 changes: 60 additions & 33 deletions source/delegate/contracts/Delegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,18 @@ import { Ownable } from "solady/src/auth/Ownable.sol";
import { SafeTransferLib } from "solady/src/utils/SafeTransferLib.sol";

/**
* @title Delegate: Deployable Trading Rules for the AirSwap Network
* @notice Supports fungible tokens (ERC-20)
* @title AirSwap: Delegated On-chain Trading Rules
* @notice Supports ERC-20 tokens
* @dev inherits IDelegate, Ownable and uses SafeTransferLib
*/
contract Delegate is IDelegate, Ownable {
// The Swap contract to be used to settle trades
// The SwapERC20 contract to be used to execute orders
ISwapERC20 public swapERC20;

// Mapping of sender to senderToken to to signerToken to Rule
mapping(address => mapping(address => mapping(address => Rule))) public rules;

// Mapping of signer to authorized signatory
// Mapping of sender to authorized manager
mapping(address => address) public authorized;

/**
Expand All @@ -32,68 +32,91 @@ contract Delegate is IDelegate, Ownable {
}

/**
* @notice Set a Trading Rule
* @param _senderToken address Address of an ERC-20 token the consumer would send
* @param _senderRuleAmount uint256 Maximum amount of ERC-20 token the sender wants to swap
* @param _signerToken address Address of an ERC-20 token the delegate would recieve
* @param _signerAmount uint256 Minimum amount of ERC-20 token the delegate would recieve
* @notice Set a Rule
* @param _senderWallet The address of the sender's wallet
* @param _senderToken address ERC-20 token the sender would transfer
* @param _senderAmount uint256 Maximum sender amount for the rule
* @param _signerToken address ERC-20 token the signer would transfer
* @param _signerAmount uint256 Maximum signer amount for the rule
* @param _expiry uint256 Expiry in seconds since 1 January 1970
*/
function setRule(
address _senderWallet,
address _senderToken,
uint256 _senderRuleAmount,
uint256 _senderAmount,
address _signerToken,
uint256 _signerAmount,
uint256 _ruleExpiry
uint256 _expiry
) external {
if (authorized[_senderWallet] != address(0)) {
if (authorized[_senderWallet] != msg.sender) revert SenderInvalid();
// If an authorized manager is set, message sender must be the manager
if (msg.sender != authorized[_senderWallet]) revert SenderInvalid();
} else {
if (_senderWallet != msg.sender) revert SenderInvalid();
// Otherwise message sender must be the sender wallet
if (msg.sender != _senderWallet) revert SenderInvalid();
}

// Set the rule. Overwrites an existing rule.
rules[_senderWallet][_senderToken][_signerToken] = Rule(
_senderWallet,
_senderToken,
_senderRuleAmount,
_senderAmount,
0,
_signerToken,
_signerAmount,
_ruleExpiry
_expiry
);

emit SetRule(
_senderWallet,
_senderToken,
_senderRuleAmount,
0,
_senderAmount,
_signerToken,
_signerAmount,
_ruleExpiry
_expiry
);
}

/**
* @notice Unset a Trading Rule
* @dev only callable by the owner of the contract, removes from a mapping
* @param _senderToken address Address of an ERC-20 token the sender would send
* @param _signerToken address Address of an ERC-20 token the delegate would receive
* @notice Unset rule
* @param _senderWallet The address of the sender's wallet
* @param _senderToken address sender token of the rule
* @param _signerToken address signer token of the rule
*/
function unsetRule(
address _senderWallet,
address _senderToken,
address _signerToken
) external {
if (authorized[_senderWallet] != address(0)) {
if (authorized[_senderWallet] != msg.sender) revert SenderInvalid();
// If an authorized manager is set, the message sender must be the manager
if (msg.sender != authorized[_senderWallet]) revert SenderInvalid();
} else {
if (_senderWallet != msg.sender) revert SenderInvalid();
// Otherwise the message sender must be the sender wallet
if (msg.sender != _senderWallet) revert SenderInvalid();
}

// Delete the rule
delete rules[_senderWallet][_senderToken][_signerToken];

emit UnsetRule(_senderWallet, _senderToken, _signerToken);
}

/**
* @notice Atomic ERC20 Swap
* @notice forwards to the underlying SwapERC20 contract
* @param _senderWallet address Wallet to receive sender proceeds
* @param _nonce uint256 Unique and should be sequential
* @param _expiry uint256 Expiry in seconds since 1 January 1970
* @param _signerWallet address Wallet of the signer
* @param _signerToken address ERC20 token transferred from the signer
* @param _signerAmount uint256 Amount transferred from the signer
* @param _senderToken address ERC20 token transferred from the sender
* @param _senderAmount uint256 Amount transferred from the sender
* @param _v uint8 "v" value of the ECDSA signature
* @param _r bytes32 "r" value of the ECDSA signature
* @param _s bytes32 "s" value of the ECDSA signature
*/
function swap(
address _senderWallet,
uint256 _nonce,
Expand All @@ -108,29 +131,31 @@ contract Delegate is IDelegate, Ownable {
bytes32 _s
) external {
Rule storage rule = rules[_senderWallet][_senderToken][_signerToken];

if (
_signerAmount <
(rule.signerAmount * (rule.senderRuleAmount - rule.senderFilledAmount)) /
rule.senderRuleAmount
(rule.signerAmount * (rule.senderAmount - rule.senderFilledAmount)) /
rule.senderAmount
) {
revert InvalidSignerAmount();
revert SignerAmountInvalid();
}
if (rule.ruleExpiry < block.timestamp) revert RuleExpired();
if (rule.expiry < block.timestamp) revert RuleExpired();

if (_senderAmount > (rule.senderRuleAmount - rule.senderFilledAmount)) {
revert InvalidSenderAmount();
if (_senderAmount > (rule.senderAmount - rule.senderFilledAmount)) {
revert SenderAmountInvalid();
}

// Transfer the sender token to this contract
SafeTransferLib.safeTransferFrom(
_senderToken,
_senderWallet,
address(this),
_senderAmount
);

// Approve the SwapERC20 contract to transfer the sender token
ERC20(_senderToken).approve(address(swapERC20), _senderAmount);

// Execute the swap
swapERC20.swapLight(
_nonce,
_expiry,
Expand All @@ -144,16 +169,18 @@ contract Delegate is IDelegate, Ownable {
_s
);

// Transfer the signer token to the sender wallet
SafeTransferLib.safeTransfer(_signerToken, _senderWallet, _signerAmount);

// Update the filled amount
rules[_senderWallet][_senderToken][_signerToken]
.senderFilledAmount += _senderAmount;
emit DelegateSwap(_nonce, _signerWallet);
}

/**
* @notice Authorize a wallet to manage rules
* @param _manager address Wallet of the signatory to authorize
* @param _manager address Wallet of the manager to authorize
* @dev Emits an Authorize event
*/
function authorize(address _manager) external {
Expand All @@ -177,7 +204,7 @@ contract Delegate is IDelegate, Ownable {
* @param _swapERC20 ISwapERC20 The SwapERC20 contract
*/
function setSwapERC20Contract(ISwapERC20 _swapERC20) external onlyOwner {
if (address(_swapERC20) == address(0)) revert InvalidAddress();
if (address(_swapERC20) == address(0)) revert AddressInvalid();
swapERC20 = _swapERC20;
}
}
31 changes: 15 additions & 16 deletions source/delegate/contracts/interfaces/IDelegate.sol
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,45 @@ pragma solidity 0.8.23;

interface IDelegate {
struct Rule {
address sender;
address senderWallet;
address senderToken;
uint256 senderRuleAmount;
uint256 senderAmount;
uint256 senderFilledAmount;
address signerToken;
uint256 signerAmount;
uint256 ruleExpiry;
uint256 expiry;
}

error RuleExpired();
error InvalidAddress();
error InvalidSenderAmount();
error InvalidSignerAmount();
error ManagerInvalid();
error SenderInvalid();
error TransferFromFailed();

event Authorize(address signatory, address signer);
event DelegateSwap(uint256 nonce, address signerWallet);
event Revoke(address tmp, address signer);

event SetRule(
address senderWallet,
address senderToken,
uint256 senderRuleAmount,
uint256 senderFilledAmount,
uint256 senderAmount,
address signerToken,
uint256 signerAmount,
uint256 ruleExpiry
uint256 expiry
);

event UnsetRule(address signer, address signerToken, address senderToken);

error AddressInvalid();
error RuleExpired();
error SenderAmountInvalid();
error SignerAmountInvalid();
error SenderInvalid();
error ManagerInvalid();
error TransferFromFailed();

function setRule(
address sender,
address senderToken,
uint256 senderRuleAmount,
uint256 senderAmount,
address signerToken,
uint256 signerAmount,
uint256 ruleExpiry
uint256 expiry
) external;

function swap(
Expand Down
1 change: 1 addition & 0 deletions source/delegate/deploys-blocks.js.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
declare module '@airswap/delegate/deploys-blocks.js'
1 change: 0 additions & 1 deletion source/delegate/deploys.js.d.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
declare module '@airswap/delegate/deploys.js'
declare module '@airswap/delegate/deploys-blocks.js'
6 changes: 2 additions & 4 deletions source/delegate/package.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"name": "@airswap/delegate",
"version": "4.3.0-beta.0",
"description": "AirSwap: Delegate On-chain Trading Rules",
"description": "AirSwap: Delegated On-chain Trading Rules",
"license": "MIT",
"repository": {
"type": "git",
Expand All @@ -22,10 +22,8 @@
"test": "hardhat test",
"test:ci": "hardhat test",
"deploy": "hardhat run ./scripts/deploy.js",
"verify": "hardhat run ./scripts/verify.js",
"owners": "hardhat run ./scripts/owner.js",
"migrate": "hardhat run ./scripts/migrate.js",
"balances": "hardhat run ./scripts/balances.js"
"verify": "hardhat run ./scripts/verify.js"
},
"devDependencies": {
"@airswap/utils": "4.3.3",
Expand Down
10 changes: 4 additions & 6 deletions source/delegate/test/Delegate.js
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,6 @@ describe('Delegate Unit', () => {
sender.address,
senderToken.address,
DEFAULT_SENDER_AMOUNT,
0,
signerToken.address,
DEFAULT_SIGNER_AMOUNT,
RULE_EXPIRY
Expand Down Expand Up @@ -205,7 +204,6 @@ describe('Delegate Unit', () => {
sender.address,
senderToken.address,
DEFAULT_SENDER_AMOUNT,
0,
signerToken.address,
DEFAULT_SIGNER_AMOUNT,
RULE_EXPIRY
Expand Down Expand Up @@ -252,7 +250,7 @@ describe('Delegate Unit', () => {
signerToken.address
)

expect(rule.senderRuleAmount.toString()).to.equal(DEFAULT_SENDER_AMOUNT)
expect(rule.senderAmount.toString()).to.equal(DEFAULT_SENDER_AMOUNT)
})

it('unsetting a Rule updates the rule balance', async () => {
Expand Down Expand Up @@ -283,7 +281,7 @@ describe('Delegate Unit', () => {
signerToken.address
)

expect(rule.senderRuleAmount.toString()).to.equal('0')
expect(rule.senderAmount.toString()).to.equal('0')
})
})

Expand Down Expand Up @@ -413,7 +411,7 @@ describe('Delegate Unit', () => {

await expect(
delegate.connect(signer).swap(sender.address, ...order)
).to.be.revertedWith('InvalidSenderAmount')
).to.be.revertedWith('SenderAmountInvalid')
})

it('fails to swap with insufficient signer amount on Rule', async () => {
Expand Down Expand Up @@ -453,7 +451,7 @@ describe('Delegate Unit', () => {

await expect(
delegate.connect(signer).swap(sender.address, ...order)
).to.be.revertedWith('InvalidSignerAmount')
).to.be.revertedWith('SignerAmountInvalid')
})

it('fails to swap with a rule expired', async () => {
Expand Down
Loading